diff --git a/.rubocop.sorbet.yml b/.rubocop.sorbet.yml index 3f2c3973..d5082195 100644 --- a/.rubocop.sorbet.yml +++ b/.rubocop.sorbet.yml @@ -26,6 +26,7 @@ Sorbet/EnforceSigilOrder: Sorbet/EnforceSignatures: Enabled: true + Style: rbs Exclude: - lib/cli/kit/sorbet_runtime_stub.rb - "test/**/*" diff --git a/.rubocop.yml b/.rubocop.yml index 4c74143c..9c9cdfef 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -4,8 +4,7 @@ inherit_gem: inherit_from: - .rubocop.sorbet.yml -require: - - rubocop-sorbet +plugins: rubocop-sorbet AllCops: Exclude: diff --git a/Gemfile b/Gemfile index 678034db..06504cce 100644 --- a/Gemfile +++ b/Gemfile @@ -15,7 +15,7 @@ group :development, :test do end group :typecheck do - gem 'sorbet-static-and-runtime' + gem 'sorbet-static' gem 'tapioca', require: false end diff --git a/Gemfile.lock b/Gemfile.lock index 8e0b2479..d44afe36 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -15,7 +15,7 @@ GEM cli-ui (2.4.0) docile (1.4.1) erubi (1.13.1) - json (2.13.2) + json (2.15.1) language_server-protocol (3.17.0.5) lint_roller (1.1.0) logger (1.7.0) @@ -33,18 +33,18 @@ GEM parser (3.3.9.0) ast (~> 2.4.1) racc - prism (1.4.0) + prism (1.5.1) racc (1.8.1) rainbow (3.1.1) rake (13.3.0) rbi (0.3.6) prism (~> 1.0) rbs (>= 3.4.4) - rbs (3.9.4) + rbs (3.9.5) logger - regexp_parser (2.10.0) - rexml (3.4.1) - rubocop (1.79.0) + regexp_parser (2.11.3) + rexml (3.4.4) + rubocop (1.81.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) @@ -52,11 +52,10 @@ GEM parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) - rubocop-ast (>= 1.46.0, < 2.0) + rubocop-ast (>= 1.47.1, < 2.0) ruby-progressbar (~> 1.7) - tsort (>= 0.2.0) unicode-display_width (>= 2.4.0, < 4.0) - rubocop-ast (1.46.0) + rubocop-ast (1.47.1) parser (>= 3.3.7.2) prism (~> 1.4) rubocop-rake (0.7.1) @@ -75,15 +74,15 @@ GEM simplecov_json_formatter (~> 0.1) simplecov-html (0.13.2) simplecov_json_formatter (0.1.4) - sorbet (0.5.12358) - sorbet-static (= 0.5.12358) - sorbet-runtime (0.5.12358) - sorbet-static (0.5.12358-aarch64-linux) - sorbet-static (0.5.12358-universal-darwin) - sorbet-static (0.5.12358-x86_64-linux) - sorbet-static-and-runtime (0.5.12358) - sorbet (= 0.5.12358) - sorbet-runtime (= 0.5.12358) + sorbet (0.6.12627) + sorbet-static (= 0.6.12627) + sorbet-runtime (0.6.12627) + sorbet-static (0.6.12627-aarch64-linux) + sorbet-static (0.6.12627-universal-darwin) + sorbet-static (0.6.12627-x86_64-linux) + sorbet-static-and-runtime (0.6.12627) + sorbet (= 0.6.12627) + sorbet-runtime (= 0.6.12627) spoom (1.6.3) erubi (>= 1.10.0) prism (>= 0.28.0) @@ -102,10 +101,9 @@ GEM thor (>= 1.2.0) yard-sorbet thor (1.4.0) - tsort (0.2.0) - unicode-display_width (3.1.4) - unicode-emoji (~> 4.0, >= 4.0.4) - unicode-emoji (4.0.4) + unicode-display_width (3.2.0) + unicode-emoji (~> 4.1) + unicode-emoji (4.1.0) yard (0.9.37) yard-sorbet (0.9.0) sorbet-runtime @@ -131,7 +129,7 @@ DEPENDENCIES rubocop-shopify rubocop-sorbet simplecov - sorbet-static-and-runtime + sorbet-static tapioca BUNDLED WITH diff --git a/Rakefile b/Rakefile index 4b2b0406..f6464aea 100644 --- a/Rakefile +++ b/Rakefile @@ -2,7 +2,6 @@ $LOAD_PATH.unshift(File.expand_path('../lib', __FILE__)) require 'rake/testtask' require 'rubocop/rake_task' require 'bundler/gem_tasks' -require 'sorbet-runtime' unless ENV['BUNDLE_WITHOUT'] == 'typecheck' TEST_ROOT = File.expand_path('../test', __FILE__) diff --git a/bin/testunit b/bin/testunit index c55059c4..bea74c3a 100755 --- a/bin/testunit +++ b/bin/testunit @@ -2,7 +2,6 @@ require 'rubygems' require 'bundler/setup' -require 'sorbet-runtime' unless ENV['BUNDLE_WITHOUT'] == 'typecheck' root = File.expand_path('../..', __FILE__) CLI_TEST_ROOT = root + '/test' diff --git a/examples/minimal/example.rb b/examples/minimal/example.rb index 7771e8ea..d73ea917 100755 --- a/examples/minimal/example.rb +++ b/examples/minimal/example.rb @@ -11,7 +11,7 @@ registry = CommandRegistry.new(default: 'hello') registry.add( Class.new(BaseCommand) do - sig { params(_args: T::Array[String], _name: String).void } + #: (Array[String], String) -> void def call(_args, _name) puts 'hello, world!' end diff --git a/examples/single-file/example.rb b/examples/single-file/example.rb index ec222a89..50a686d0 100755 --- a/examples/single-file/example.rb +++ b/examples/single-file/example.rb @@ -15,7 +15,7 @@ module Commands Registry = CLI::Kit::CommandRegistry.new(default: 'hello') class << self - sig { params(const: Symbol, cmd: String, path: String, lamda_const: T.proc.returns(Example::Command)).void } + #: (Symbol const, String cmd, String path, ^-> Example::Command lamda_const) -> void def register(const, cmd, path, lamda_const) autoload(const, path) Registry.add(lamda_const, cmd) @@ -25,7 +25,8 @@ def register(const, cmd, path, lamda_const) end class Hello < Example::Command - sig { override.params(_args: T::Array[String], _name: String).void } + # @override + #: (Array[String] _args, String _name) -> void def call(_args, _name) puts 'hello, world!' end @@ -34,7 +35,7 @@ def call(_args, _name) module EntryPoint class << self - sig { params(args: T::Array[String]).void } + #: (Array[String] args) -> void def call(args) cmd, command_name, args = Example::Resolver.call(args) Example::Executor.call(cmd, command_name, args) diff --git a/gen/lib/gen/commands.rb b/gen/lib/gen/commands.rb index 8d4d16ee..9dfc78fc 100644 --- a/gen/lib/gen/commands.rb +++ b/gen/lib/gen/commands.rb @@ -4,16 +4,10 @@ module Gen module Commands - extend T::Sig - Registry = CLI::Kit::CommandRegistry.new(default: 'help') class << self - extend T::Sig - - sig do - params(const: Symbol, cmd: String, path: String, lamda_const: T.proc.returns(T.class_of(Gen::Command))).void - end + #: (Symbol const, String cmd, String path, ^-> singleton(Gen::Command) lamda_const) -> void def register(const, cmd, path, lamda_const) autoload(const, path) Registry.add(lamda_const, cmd) diff --git a/gen/lib/gen/commands/help.rb b/gen/lib/gen/commands/help.rb index b533488d..c95be679 100644 --- a/gen/lib/gen/commands/help.rb +++ b/gen/lib/gen/commands/help.rb @@ -5,11 +5,9 @@ module Gen module Commands class Help < Gen::Command - extend T::Sig - desc('Show help for a command, or this page') - sig { params(args: T::Array[String], _name: String).void } + #: (Array[String] args, String _name) -> void def call(args, _name) Gen::Help.generate(args) end diff --git a/gen/lib/gen/commands/new.rb b/gen/lib/gen/commands/new.rb index a003d87d..169639f2 100644 --- a/gen/lib/gen/commands/new.rb +++ b/gen/lib/gen/commands/new.rb @@ -5,8 +5,6 @@ module Gen module Commands class New < Gen::Command - extend T::Sig - command_name('new') desc('Create a new project') long_desc(<<~LONGDESC) @@ -18,15 +16,13 @@ class New < Gen::Command example('mycliapp', "create a new project called 'mycliapp'") class Opts < CLI::Kit::Opts - extend(T::Sig) - - sig { returns(String) } + #: -> String def project_name position! end end - sig { params(op: Opts, _name: T.untyped).returns(T.untyped) } + #: (Opts op, untyped _name) -> untyped def invoke(op, _name) Gen::Generator.run(op.project_name) end diff --git a/gen/lib/gen/entry_point.rb b/gen/lib/gen/entry_point.rb index 71d21012..81fb8495 100644 --- a/gen/lib/gen/entry_point.rb +++ b/gen/lib/gen/entry_point.rb @@ -4,12 +4,8 @@ module Gen module EntryPoint - extend T::Sig - class << self - extend T::Sig - - sig { params(args: T::Array[String]).void } + #: (Array[String] args) -> void def call(args) cmd, command_name, args = Gen::Resolver.call(args) Gen::Executor.call(cmd, command_name, args) diff --git a/gen/lib/gen/generator.rb b/gen/lib/gen/generator.rb index 4f196210..dbe713f1 100644 --- a/gen/lib/gen/generator.rb +++ b/gen/lib/gen/generator.rb @@ -8,12 +8,8 @@ module Gen class Generator - extend T::Sig - class << self - extend T::Sig - - sig { params(project_name: String).void } + #: (String project_name) -> void def run(project_name) new(project_name).run end @@ -44,7 +40,7 @@ def run(project_name) }.freeze private_constant :BUNDLER_TRANSLATIONS - sig { params(project_name: String).void } + #: (String project_name) -> void def initialize(project_name) raise( CLI::Kit::Abort, @@ -54,7 +50,7 @@ def initialize(project_name) @title_case_project_name = @project_name.sub(/^./, &:upcase) end - sig { void } + #: -> void def run vendor = ask_vendor? create_project_dir @@ -68,12 +64,12 @@ def run private - sig { returns(T::Boolean) } + #: -> bool def ask_vendor? return true if ENV['DEPS'] == 'vendor' return false if ENV['DEPS'] == 'bundler' - vendor = T.let(nil, T.nilable(String)) + vendor = nil #: String? CLI::UI::Frame.open('Configuration') do q = 'How would you like the application to consume {{command:cli-kit}} and {{command:cli-ui}}?' vendor = CLI::UI::Prompt.ask(q) do |c| @@ -84,7 +80,7 @@ def ask_vendor? vendor == 'vendor' end - sig { void } + #: -> void def create_project_dir info(create: '') FileUtils.mkdir(@project_name) @@ -92,7 +88,7 @@ def create_project_dir error("directory already exists: #{@project_name}") end - sig { params(translations: T::Hash[String, T.any(FalseClass, String)]).void } + #: (translations: Hash[String, (FalseClass | String)]) -> void def copy_files(translations:) each_template_file do |source_name| target_name = translations.fetch(source_name, source_name) @@ -115,7 +111,7 @@ def copy_files(translations:) end end - sig { void } + #: -> void def update_deps Dir.mktmpdir do |tmp| clone(tmp, 'cli-ui') @@ -127,7 +123,7 @@ def update_deps end end - sig { params(dir: String, repo: String).void } + #: (String dir, String repo) -> void def clone(dir, repo) info(clone: repo) out, stat = Open3.capture2e('git', '-C', dir, 'clone', "https://github.com/shopify/#{repo}") @@ -137,7 +133,7 @@ def clone(dir, repo) end end - sig { params(block: T.proc.params(rel_path: String).void).void } + #: { (String rel_path) -> void } -> void def each_template_file(&block) root = Pathname.new(TEMPLATE_ROOT) Dir.glob("#{TEMPLATE_ROOT}/**/*").each do |f| @@ -146,7 +142,7 @@ def each_template_file(&block) end end - sig { params(s: String).returns(String) } + #: (String s) -> String def apply_template_variables(s) s .gsub('__app__', @project_name) @@ -155,19 +151,19 @@ def apply_template_variables(s) .gsub('__cli-ui-version__', cli_ui_version) end - sig { returns(String) } + #: -> String def cli_kit_version require 'cli/kit/version' CLI::Kit::VERSION.to_s end - sig { returns(String) } + #: -> String def cli_ui_version require 'cli/ui/version' CLI::UI::VERSION.to_s end - sig { params(create: T.nilable(String), clone: T.nilable(String), run: T.nilable(String)).void } + #: (?create: String?, ?clone: String?, ?run: String?) -> void def info(create: nil, clone: nil, run: nil) if clone puts(CLI::UI.fmt("\t{{bold:{{yellow:clone}}\t#{clone}}}")) @@ -178,7 +174,7 @@ def info(create: nil, clone: nil, run: nil) end end - sig { params(msg: String).void } + #: (String msg) -> void def error(msg) raise(CLI::Kit::Abort, msg) end diff --git a/gen/lib/gen/help.rb b/gen/lib/gen/help.rb index b1bb8dba..24a7595a 100644 --- a/gen/lib/gen/help.rb +++ b/gen/lib/gen/help.rb @@ -4,12 +4,8 @@ module Gen module Help - extend T::Sig - class << self - extend T::Sig - - sig { params(path: T::Array[String], to: IO).void } + #: (Array[String] path, ?to: IO) -> void def generate(path, to: STDOUT) case path.size when 0 @@ -21,7 +17,7 @@ def generate(path, to: STDOUT) end end - sig { params(to: IO).void } + #: (?to: IO) -> void def generate_toplevel(to: STDOUT) to.write(CLI::UI.fmt(<<~HELP)) {{bold:{{command:cli-kit}} generates new cli-kit apps.}} @@ -44,7 +40,7 @@ def generate_toplevel(to: STDOUT) end end - sig { params(cmd_name: String, to: IO).void } + #: (String cmd_name, ?to: IO) -> void def generate_command_help(cmd_name, to: STDOUT) klass = Gen::Commands::Registry.resolved_commands[cmd_name] unless klass diff --git a/lib/cli/kit.rb b/lib/cli/kit.rb index 91f3a572..920d48fd 100644 --- a/lib/cli/kit.rb +++ b/lib/cli/kit.rb @@ -1,17 +1,10 @@ # typed: true require 'cli/ui' - -unless defined?(T) - require('cli/kit/sorbet_runtime_stub') -end - require 'cli/kit/core_ext' module CLI module Kit - extend T::Sig - autoload :Args, 'cli/kit/args' autoload :BaseCommand, 'cli/kit/base_command' autoload :CommandRegistry, 'cli/kit/command_registry' @@ -33,6 +26,9 @@ module Kit EXIT_BUG = 1 EXIT_SUCCESS = 0 + UNTYPED_NIL = nil #: untyped + private_constant(:UNTYPED_NIL) + # Abort, Bug, AbortSilent, and BugSilent are four ways of immediately bailing # on command-line execution when an unrecoverable error occurs. # @@ -76,9 +72,7 @@ module Kit GenericAbort = Class.new(Exception) # rubocop:disable Lint/InheritException class Abort < GenericAbort # bug:false; silent: false - extend(T::Sig) - - sig { returns(T::Boolean) } + #: -> bool def bug? false end @@ -88,31 +82,25 @@ class Bug < GenericAbort # bug:true; silent:false end class BugSilent < GenericAbort # bug:true; silent:true - extend(T::Sig) - - sig { returns(T::Boolean) } + #: -> bool def silent? true end end class AbortSilent < GenericAbort # bug:false; silent:true - extend(T::Sig) - - sig { returns(T::Boolean) } + #: -> bool def bug? false end - sig { returns(T::Boolean) } + #: -> bool def silent? true end end class << self - extend T::Sig - # Mirrors the API of Kernel#raise, but with the addition of a few new # optional keyword arguments. `bug` and `silent` attach metadata to the # exception being raised, which is interpreted later in the ErrorHandler to @@ -121,31 +109,25 @@ class << self # `depth` is used to trim leading elements of the backtrace. If you wrap # this method in your own wrapper, you'll want to pass `depth: 2`, for # example. - sig do - params( - exception: T.any(Class, String, Exception), - string: T.untyped, - array: T.nilable(T::Array[String]), - cause: T.nilable(Exception), - bug: T.nilable(T::Boolean), - silent: T.nilable(T::Boolean), - depth: Integer, - ).returns(T.noreturn) - end + #: (?(Class | String | Exception) exception, ?untyped string, ?Array[String]? array, ?cause: Exception?, ?bug: bool?, ?silent: bool?, ?depth: Integer) -> bot def raise( # default arguments - exception = T.unsafe(nil), string = T.unsafe(nil), array = T.unsafe(nil), cause: $ERROR_INFO, + exception = UNTYPED_NIL, + string = UNTYPED_NIL, + array = UNTYPED_NIL, + cause: $ERROR_INFO, # new arguments bug: nil, silent: nil, depth: 1 ) + k = Kernel #: as untyped if array - T.unsafe(Kernel).raise(exception, string, array, cause: cause) + k.raise(exception, string, array, cause: cause) elsif string - T.unsafe(Kernel).raise(exception, string, Kernel.caller(depth), cause: cause) + k.raise(exception, string, Kernel.caller(depth), cause: cause) elsif exception.is_a?(String) - T.unsafe(Kernel).raise(RuntimeError, exception, Kernel.caller(depth), cause: cause) + k.raise(RuntimeError, exception, Kernel.caller(depth), cause: cause) else - T.unsafe(Kernel).raise(exception, exception.message, Kernel.caller(depth), cause: cause) + k.raise(exception, exception.message, Kernel.caller(depth), cause: cause) end rescue Exception => e # rubocop:disable Lint/RescueException e.bug!(bug) unless bug.nil? diff --git a/lib/cli/kit/args/definition.rb b/lib/cli/kit/args/definition.rb index 68628866..7f52b5b1 100644 --- a/lib/cli/kit/args/definition.rb +++ b/lib/cli/kit/args/definition.rb @@ -6,24 +6,22 @@ module CLI module Kit module Args class Definition - extend T::Sig - Error = Class.new(Args::Error) ConflictingFlag = Class.new(Error) InvalidFlag = Class.new(Error) InvalidLookup = Class.new(Error) InvalidPosition = Class.new(Error) - sig { returns(T::Array[Flag]) } + #: Array[Flag] attr_reader :flags - sig { returns(T::Array[Option]) } + #: Array[Option] attr_reader :options - sig { returns(T::Array[Position]) } + #: Array[Position] attr_reader :positions - sig { params(name: Symbol, short: T.nilable(String), long: T.nilable(String), desc: T.nilable(String)).void } + #: (Symbol name, ?short: String?, ?long: String?, ?desc: String?) -> void def add_flag(name, short: nil, long: nil, desc: nil) short, long = strip_prefixes_and_validate(short, long) flag = Flag.new(name: name, short: short, long: long, desc: desc) @@ -31,18 +29,7 @@ def add_flag(name, short: nil, long: nil, desc: nil) @flags << flag end - sig do - params( - name: Symbol, short: T.nilable(String), long: T.nilable(String), - desc: T.nilable(String), - default: T.any( - NilClass, - String, T.proc.returns(String), - T::Array[String], T.proc.returns(T::Array[String]) - ), - required: T::Boolean, multi: T::Boolean - ).void - end + #: (Symbol name, ?short: String?, ?long: String?, ?desc: String?, ?default: (String | ^-> String | Array[String] | ^-> Array[String])?, ?required: bool, ?multi: bool) -> void def add_option(name, short: nil, long: nil, desc: nil, default: nil, required: false, multi: false) short, long = strip_prefixes_and_validate(short, long) option = Option.new( @@ -53,20 +40,7 @@ def add_option(name, short: nil, long: nil, desc: nil, default: nil, required: f @options << option end - sig do - params( - name: Symbol, - required: T::Boolean, - multi: T::Boolean, - desc: T.nilable(String), - default: T.any(NilClass, String, T.proc.returns(String)), - skip: T.any( - NilClass, - T.proc.returns(T::Boolean), - T.proc.params(arg0: String).returns(T::Boolean), - ), - ).void - end + #: (Symbol name, required: bool, multi: bool, ?desc: String?, ?default: (String | ^-> String)?, ?skip: (^-> bool | ^(String arg0) -> bool)?) -> void def add_position(name, required:, multi:, desc: nil, default: nil, skip: nil) position = Position.new( name: name, desc: desc, required: required, multi: multi, @@ -77,7 +51,7 @@ def add_position(name, required:, multi:, desc: nil, default: nil, skip: nil) @positions << position end - sig { void } + #: -> void def initialize @flags = [] @options = [] @@ -88,19 +62,15 @@ def initialize end module OptBase - extend T::Sig - - sig { returns(Symbol) } + #: Symbol attr_reader :name - sig { returns(T.nilable(String)) } + #: String? attr_reader :desc end module OptValue - extend T::Sig - - sig { returns(T.any(NilClass, String, T::Array[String])) } + #: -> (String | Array[String])? def default if @default.is_a?(Proc) @default.call @@ -109,43 +79,42 @@ def default end end - sig { returns(T::Boolean) } + #: -> bool def dynamic_default? @default.is_a?(Proc) end - sig { returns(T::Boolean) } + #: -> bool def required? @required end - sig { returns(T::Boolean) } + #: -> bool def multi? @multi end - sig { returns(T::Boolean) } + #: -> bool def optional? !required? end end class Flag - extend T::Sig include OptBase - sig { returns(T.nilable(String)) } + #: String? attr_reader :short - sig { returns(T.nilable(String)) } + #: String? attr_reader :long - sig { returns(String) } + #: -> String def as_written_by_user long ? "--#{long}" : "-#{short}" end - sig { params(name: Symbol, short: T.nilable(String), long: T.nilable(String), desc: T.nilable(String)).void } + #: (name: Symbol, ?short: String?, ?long: String?, ?desc: String?) -> void def initialize(name:, short: nil, long: nil, desc: nil) if long&.start_with?('-') || short&.start_with?('-') raise(ArgumentError, 'invalid - prefix') @@ -159,24 +128,10 @@ def initialize(name:, short: nil, long: nil, desc: nil) end class Position - extend T::Sig include OptBase include OptValue - sig do - params( - name: Symbol, - desc: T.nilable(String), - required: T::Boolean, - multi: T::Boolean, - default: T.any(NilClass, String, T.proc.returns(String)), - skip: T.any( - NilClass, - T.proc.returns(T::Boolean), - T.proc.params(arg0: String).returns(T::Boolean), - ), - ).void - end + #: (name: Symbol, desc: String?, required: bool, multi: bool, ?default: (String | ^-> String)?, ?skip: (^-> bool | ^(String arg0) -> bool)?) -> void def initialize(name:, desc:, required:, multi:, default: nil, skip: nil) if multi && (default || required) raise(ArgumentError, 'multi-valued positions cannot have a default or required value') @@ -190,34 +145,24 @@ def initialize(name:, desc:, required:, multi:, default: nil, skip: nil) @skip = skip end - sig { params(arg: String).returns(T::Boolean) } + #: (String arg) -> bool def skip?(arg) if @skip.nil? false - elsif T.must(@skip).arity == 0 - T.cast(@skip, T.proc.returns(T::Boolean)).call + elsif @skip.arity == 0 + prc = @skip #: as ^() -> bool + prc.call else - T.cast(@skip, T.proc.params(arg0: String).returns(T::Boolean)).call(arg) + prc = @skip #: as ^(String) -> bool + prc.call(arg) end end end class Option < Flag - extend T::Sig include OptValue - sig do - params( - name: Symbol, short: T.nilable(String), long: T.nilable(String), - desc: T.nilable(String), - default: T.any( - NilClass, - String, T.proc.returns(String), - T::Array[String], T.proc.returns(T::Array[String]) - ), - required: T::Boolean, multi: T::Boolean - ).void - end + #: (name: Symbol, ?short: String?, ?long: String?, ?desc: String?, ?default: (String | ^-> String | Array[String] | ^-> Array[String])?, ?required: bool, ?multi: bool) -> void def initialize(name:, short: nil, long: nil, desc: nil, default: nil, required: false, multi: false) if multi && required raise(ArgumentError, 'multi-valued options cannot have a required value') @@ -230,7 +175,7 @@ def initialize(name:, short: nil, long: nil, desc: nil, default: nil, required: end end - sig { params(name: Symbol).returns(T.nilable(Flag)) } + #: (Symbol name) -> Flag? def lookup_flag(name) flagopt = @by_name[name] if flagopt.class == Flag @@ -238,7 +183,7 @@ def lookup_flag(name) end end - sig { params(name: Symbol).returns(T.nilable(Option)) } + #: (Symbol name) -> Option? def lookup_option(name) flagopt = @by_name[name] if flagopt.class == Option @@ -246,21 +191,21 @@ def lookup_option(name) end end - sig { params(name: String).returns(T.any(Flag, Option, NilClass)) } + #: (String name) -> (Flag | Option)? def lookup_short(name) raise(InvalidLookup, "invalid '-' prefix") if name.start_with?('-') @by_short[name] end - sig { params(name: String).returns(T.any(Flag, Option, NilClass)) } + #: (String name) -> (Flag | Option)? def lookup_long(name) raise(InvalidLookup, "invalid '-' prefix") if name.start_with?('-') @by_long[name] end - sig { params(name: Symbol).returns(T.nilable(Position)) } + #: (Symbol name) -> Position? def lookup_position(name) position = @by_name[name] if position.class == Position @@ -270,12 +215,12 @@ def lookup_position(name) private - sig { params(position: Position).void } + #: (Position position) -> void def validate_order(position) raise(InvalidPosition, 'Cannot have any more positional arguments after multi') if @positions.last&.multi? end - sig { params(short: String).returns(String) } + #: (String short) -> String def strip_short_prefix(short) unless short.match?(/^-[^-]/) raise(InvalidFlag, "Short flag '#{short}' does not start with '-'") @@ -287,7 +232,7 @@ def strip_short_prefix(short) short.sub(/^-/, '') end - sig { params(long: String).returns(String) } + #: (String long) -> String def strip_long_prefix(long) unless long.match?(/^--[^-]/) raise(InvalidFlag, "Long flag '#{long}' does not start with '--'") @@ -296,10 +241,7 @@ def strip_long_prefix(long) long.sub(/^--/, '') end - sig do - params(short: T.nilable(String), long: T.nilable(String)) - .returns([T.nilable(String), T.nilable(String)]) - end + #: (String? short, String? long) -> [String?, String?] def strip_prefixes_and_validate(short, long) if short.nil? && long.nil? raise(Error, 'One or more of short and long must be specified') @@ -311,7 +253,7 @@ def strip_prefixes_and_validate(short, long) [short, long] end - sig { params(flagopt: Flag).void } + #: (Flag flagopt) -> void def add_resolution(flagopt) if flagopt.short if (existing = @by_short[flagopt.short]) @@ -330,7 +272,7 @@ def add_resolution(flagopt) add_name_resolution(flagopt) end - sig { params(arg: T.any(Flag, Position)).void } + #: ((Flag | Position) arg) -> void def add_name_resolution(arg) if (existing = @by_name[arg.name]) raise(ConflictingFlag, "Flag '#{arg.name}' already defined by #{existing.name}") diff --git a/lib/cli/kit/args/evaluation.rb b/lib/cli/kit/args/evaluation.rb index ef6ce2ca..ca52cb1d 100644 --- a/lib/cli/kit/args/evaluation.rb +++ b/lib/cli/kit/args/evaluation.rb @@ -6,38 +6,31 @@ module CLI module Kit module Args class Evaluation - extend T::Sig - Error = Class.new(Args::Error) class MissingRequiredOption < Error - extend T::Sig - sig { params(name: String).void } + #: (String name) -> void def initialize(name) super("missing required option `#{name}'") end end class MissingRequiredPosition < Error - extend T::Sig - sig { void } + #: -> void def initialize super('more arguments required') end end class TooManyPositions < Error - extend T::Sig - sig { void } + #: -> void def initialize super('too many arguments') end end class FlagProxy - extend T::Sig - - sig { params(sym: Symbol).returns(T::Boolean) } + #: (Symbol sym) -> bool def method_missing(sym) flag = @evaluation.defn.lookup_flag(sym) unless flag @@ -47,21 +40,19 @@ def method_missing(sym) @evaluation.send(:lookup_flag, flag) end - sig { params(sym: Symbol, include_private: T::Boolean).returns(T::Boolean) } + #: (Symbol sym, ?bool include_private) -> bool def respond_to_missing?(sym, include_private = false) !!@evaluation.defn.lookup_flag(sym) end - sig { params(evaluation: Evaluation).void } + #: (Evaluation evaluation) -> void def initialize(evaluation) @evaluation = evaluation end end class OptionProxy - extend T::Sig - - sig { params(sym: Symbol).returns(T.any(NilClass, String, T::Array[String])) } + #: (Symbol sym) -> (String | Array[String])? def method_missing(sym) opt = @evaluation.defn.lookup_option(sym) unless opt @@ -71,21 +62,19 @@ def method_missing(sym) @evaluation.send(:lookup_option, opt) end - sig { params(sym: Symbol, include_private: T::Boolean).returns(T::Boolean) } + #: (Symbol sym, ?bool include_private) -> bool def respond_to_missing?(sym, include_private = false) !!@evaluation.defn.lookup_option(sym) end - sig { params(evaluation: Evaluation).void } + #: (Evaluation evaluation) -> void def initialize(evaluation) @evaluation = evaluation end end class PositionProxy - extend T::Sig - - sig { params(sym: Symbol).returns(T.any(NilClass, String, T::Array[String])) } + #: (Symbol sym) -> (String | Array[String])? def method_missing(sym) position = @evaluation.defn.lookup_position(sym) unless position @@ -95,57 +84,54 @@ def method_missing(sym) @evaluation.send(:lookup_position, position) end - sig { params(sym: Symbol, include_private: T::Boolean).returns(T::Boolean) } + #: (Symbol sym, ?bool include_private) -> bool def respond_to_missing?(sym, include_private = false) !!@evaluation.defn.lookup_position(sym) end - sig { params(evaluation: Evaluation).void } + #: (Evaluation evaluation) -> void def initialize(evaluation) @evaluation = evaluation end end - sig { returns(FlagProxy) } + #: -> FlagProxy def flag @flag_proxy ||= FlagProxy.new(self) end - sig { returns(OptionProxy) } + #: -> OptionProxy def opt @option_proxy ||= OptionProxy.new(self) end - sig { returns(PositionProxy) } + #: -> PositionProxy def position @position_proxy ||= PositionProxy.new(self) end - sig { returns(Definition) } + #: Definition attr_reader :defn - sig { returns(T::Array[Parser::Node]) } + #: Array[Parser::Node] attr_reader :parse - sig { returns(T::Array[String]) } + #: -> Array[String] def unparsed @unparsed ||= begin - nodes = T.cast( - parse.select { |node| node.is_a?(Parser::Node::Unparsed) }, - T::Array[Parser::Node::Unparsed], - ) + nodes = parse.select { |node| node.is_a?(Parser::Node::Unparsed) } #: as Array[Parser::Node::Unparsed] nodes.flat_map(&:value) end end - sig { params(defn: Definition, parse: T::Array[Parser::Node]).void } + #: (Definition defn, Array[Parser::Node] parse) -> void def initialize(defn, parse) @defn = defn @parse = parse check_required_options! end - sig { void } + #: -> void def check_required_options! @defn.options.each do |opt| next unless opt.required? @@ -153,56 +139,54 @@ def check_required_options! node = @parse.detect do |node| node.is_a?(Parser::Node::Option) && node.name.to_sym == opt.name end - if !node || T.cast(node, Parser::Node::Option).value.nil? + unless node + raise(MissingRequiredOption, opt.as_written_by_user) + end + + node = node #: as Parser::Node::Option + if node.value.nil? raise(MissingRequiredOption, opt.as_written_by_user) end end end - sig { void } + #: -> void def resolve_positions! args_i = 0 @position_values = Hash.new @defn.positions.each do |position| raise(MissingRequiredPosition) if position.required? && args_i >= args.size - next if args_i >= args.size || position.skip?(T.must(args[args_i])) + next if args_i >= args.size || position.skip?( + args[args_i], #: as !nil + ) if position.multi? @position_values[position.name] = args[args_i..] args_i = args.size else - @position_values[position.name] = T.must(args[args_i]) + @position_values[position.name] = args[args_i] #: as !nil args_i += 1 end end raise(TooManyPositions) if args_i < args.size end - sig { params(flag: Definition::Flag).returns(T::Boolean) } + #: (Definition::Flag flag) -> bool def lookup_flag(flag) if flag.short - flags = T.cast( - parse.select { |node| node.is_a?(Parser::Node::ShortFlag) }, - T::Array[Parser::Node::ShortFlag], - ) + flags = parse.select { |node| node.is_a?(Parser::Node::ShortFlag) } #: as Array[Parser::Node::ShortFlag] return true if flags.any? { |node| node.value == flag.short } end if flag.long - flags = T.cast( - parse.select { |node| node.is_a?(Parser::Node::LongFlag) }, - T::Array[Parser::Node::LongFlag], - ) + flags = parse.select { |node| node.is_a?(Parser::Node::LongFlag) } #: as Array[Parser::Node::LongFlag] return true if flags.any? { |node| node.value == flag.long } end false end - sig { params(opt: Definition::Option).returns(T.any(NilClass, String, T::Array[String])) } + #: (Definition::Option opt) -> (String | Array[String])? def lookup_option(opt) - opts = T.cast( - parse.select { |node| node.is_a?(Parser::Node::ShortOption) || node.is_a?(Parser::Node::LongOption) }, - T::Array[T.any(Parser::Node::ShortOption, Parser::Node::LongOption)], - ) + opts = parse.select { |node| node.is_a?(Parser::Node::ShortOption) || node.is_a?(Parser::Node::LongOption) } #: as Array[Parser::Node::ShortOption | Parser::Node::LongOption] matches = opts.select { |node| (opt.short && node.name == opt.short) || (opt.long && node.name == opt.long) } if (last = matches.last) return (opt.multi? ? matches.map(&:value) : last.value) @@ -211,20 +195,17 @@ def lookup_option(opt) opt.default end - sig { params(position: Definition::Position).returns(T.any(NilClass, String, T::Array[String])) } + #: (Definition::Position position) -> (String | Array[String])? def lookup_position(position) @position_values.fetch(position.name) { position.multi? ? [] : position.default } end private - sig { returns(T::Array[String]) } + #: -> Array[String] def args @args ||= begin - nodes = T.cast( - parse.select { |node| node.is_a?(Parser::Node::Argument) }, - T::Array[Parser::Node::Argument], - ) + nodes = parse.select { |node| node.is_a?(Parser::Node::Argument) } #: as Array[Parser::Node::Argument] nodes.map(&:value) end end diff --git a/lib/cli/kit/args/parser.rb b/lib/cli/kit/args/parser.rb index 29fb7712..cdeca383 100644 --- a/lib/cli/kit/args/parser.rb +++ b/lib/cli/kit/args/parser.rb @@ -6,32 +6,28 @@ module CLI module Kit module Args class Parser - extend T::Sig - autoload :Node, 'cli/kit/args/parser/node' Error = Class.new(Args::Error) class InvalidOptionError < Error - extend T::Sig - sig { params(option: String).void } + #: (String option) -> void def initialize(option) super("invalid option -- '#{option}'") end end class OptionRequiresAnArgumentError < Error - extend T::Sig - sig { params(option: String).void } + #: (String option) -> void def initialize(option) super("option requires an argument -- '#{option}'") end end - sig { params(tokens: T::Array[Tokenizer::Token]).returns(T::Array[Node]) } + #: (Array[Tokenizer::Token] tokens) -> Array[Node] def parse(tokens) - nodes = T.let([], T::Array[Node]) - args = T.let(tokens, T::Array[T.nilable(Tokenizer::Token)]) + nodes = [] #: Array[Node] + args = tokens #: Array[Tokenizer::Token?] args << nil # to make each_cons pass (args.last, nil) on the final round. state = :init # TODO: test that "--height -- 3" is parsed correctly. @@ -40,7 +36,10 @@ def parse(tokens) when :skip state = :init when :init - state, val = parse_token(T.must(arg), next_arg) + state, val = parse_token( + arg, #: as !nil + next_arg, + ) nodes << val when :unparsed unless arg.is_a?(Tokenizer::Token::UnparsedArgument) @@ -60,17 +59,14 @@ def parse(tokens) nodes end - sig { params(definition: Definition).void } + #: (Definition definition) -> void def initialize(definition) @defn = definition end private - sig do - params(token: Tokenizer::Token, next_token: T.nilable(Tokenizer::Token)) - .returns([Symbol, Parser::Node]) - end + #: (Tokenizer::Token token, Tokenizer::Token? next_token) -> [Symbol, Parser::Node] def parse_token(token, next_token) case token when Tokenizer::Token::LongOptionName @@ -104,7 +100,7 @@ def parse_token(token, next_token) end end - sig { params(arg: Tokenizer::Token::OptionName, next_arg: T.nilable(Tokenizer::Token)).returns(Node) } + #: (Tokenizer::Token::OptionName arg, Tokenizer::Token? next_arg) -> Node def parse_option(arg, next_arg) case next_arg when nil, Tokenizer::Token::LongOptionName, diff --git a/lib/cli/kit/args/parser/node.rb b/lib/cli/kit/args/parser/node.rb index f2bb3c7a..f56c093d 100644 --- a/lib/cli/kit/args/parser/node.rb +++ b/lib/cli/kit/args/parser/node.rb @@ -7,27 +7,23 @@ module Kit module Args class Parser class Node - extend T::Sig - - sig { void } + #: -> void def initialize end - sig { params(other: T.untyped).returns(T::Boolean) } + #: (untyped other) -> bool def ==(other) self.class == other.class end class Option < Node - extend T::Sig - - sig { returns(String) } + #: String attr_reader :name - sig { returns(String) } + #: String attr_reader :value - sig { params(name: String, value: String).void } + #: (String name, String value) -> void def initialize(name, value) @name = name @value = value @@ -35,12 +31,12 @@ def initialize(name, value) end private_class_method(:new) # don't instantiate this class directly - sig { returns(String) } + #: -> String def inspect "#<#{self.class.name} #{@name}=#{@value}>" end - sig { params(other: T.untyped).returns(T::Boolean) } + #: (untyped other) -> bool def ==(other) !!(super(other) && @value == other.value && @name == other.name) end @@ -55,22 +51,22 @@ class ShortOption < Option end class Flag < Node - sig { returns(String) } + #: String attr_reader :value - sig { params(value: String).void } + #: (String value) -> void def initialize(value) @value = value super() end private_class_method(:new) # don't instantiate this class directly - sig { returns(String) } + #: -> String def inspect "#<#{self.class.name} #{@value}>" end - sig { params(other: T.untyped).returns(T::Boolean) } + #: (untyped other) -> bool def ==(other) !!(super(other) && @value == other.value) end @@ -85,42 +81,42 @@ class ShortFlag < Flag end class Argument < Node - sig { returns(String) } + #: String attr_reader :value - sig { params(value: String).void } + #: (String value) -> void def initialize(value) @value = value super() end - sig { returns(String) } + #: -> String def inspect "#<#{self.class.name} #{@value}>" end - sig { params(other: T.untyped).returns(T::Boolean) } + #: (untyped other) -> bool def ==(other) !!(super(other) && @value == other.value) end end class Unparsed < Node - sig { returns(T::Array[String]) } + #: Array[String] attr_reader :value - sig { params(value: T::Array[String]).void } + #: (Array[String] value) -> void def initialize(value) @value = value super() end - sig { returns(String) } + #: -> String def inspect "#<#{self.class.name} #{@value.join(" ")}>" end - sig { params(other: T.untyped).returns(T::Boolean) } + #: (untyped other) -> bool def ==(other) !!(super(other) && @value == other.value) end diff --git a/lib/cli/kit/args/tokenizer.rb b/lib/cli/kit/args/tokenizer.rb index 84dcc7a9..b6dfc1eb 100644 --- a/lib/cli/kit/args/tokenizer.rb +++ b/lib/cli/kit/args/tokenizer.rb @@ -6,43 +6,37 @@ module CLI module Kit module Args module Tokenizer - extend T::Sig - Error = Class.new(Args::Error) class InvalidShortOption < Error - extend T::Sig - sig { params(short_option: String).void } + #: (String short_option) -> void def initialize(short_option) super("invalid short option: '-#{short_option}'") end end class InvalidCharInShortOption < Error - extend T::Sig - sig { params(short_option: String, char: String).void } + #: (String short_option, String char) -> void def initialize(short_option, char) super("invalid character '#{char}' in short option: '-#{short_option}'") end end class Token - extend T::Sig - - sig { returns(String) } + #: String attr_reader :value - sig { params(value: String).void } + #: (String value) -> void def initialize(value) @value = value end - sig { returns(String) } + #: -> String def inspect "#<#{self.class.name} #{@value}>" end - sig { params(other: T.untyped).returns(T::Boolean) } + #: (untyped other) -> bool def ==(other) self.class == other.class && @value == other.value end @@ -58,9 +52,7 @@ def ==(other) end class << self - extend T::Sig - - sig { params(raw_args: T::Array[String]).returns(T::Array[Token]) } + #: (Array[String] raw_args) -> Array[Token] def tokenize(raw_args) args = [] @@ -76,12 +68,17 @@ def tokenize(raw_args) mode = :unparsed when /\A--./ name, value = arg.split('=', 2) - args << Token::LongOptionName.new(T.must(T.must(name)[2..-1])) + name = name #: as !nil + args << Token::LongOptionName.new( + name[2..-1], #: as !nil + ) if value args << Token::OptionValue.new(value) end when /\A-./ - args.concat(tokenize_short_option(T.must(arg[1..-1]))) + args.concat(tokenize_short_option( + arg[1..-1], #: as !nil + )) else args << if args.last.is_a?(Token::OptionName) Token::OptionValueOrPositionalArgument.new(arg) @@ -95,7 +92,7 @@ def tokenize(raw_args) args end - sig { params(arg: String).returns(T::Array[Token]) } + #: (String arg) -> Array[Token] def tokenize_short_option(arg) args = [] mode = :init diff --git a/lib/cli/kit/base_command.rb b/lib/cli/kit/base_command.rb index 00249947..7b32efc2 100644 --- a/lib/cli/kit/base_command.rb +++ b/lib/cli/kit/base_command.rb @@ -4,28 +4,24 @@ module CLI module Kit + # @abstract class BaseCommand - extend T::Sig - extend T::Helpers include CLI::Kit::CommandHelp extend CLI::Kit::CommandHelp::ClassMethods - abstract! class << self - extend T::Sig - - sig { returns(T::Boolean) } + #: -> bool def defined? true end - sig { params(args: T::Array[String], command_name: String).void } + #: (Array[String] args, String command_name) -> void def call(args, command_name) new.call(args, command_name) end end - sig { returns(T::Boolean) } + #: -> bool def has_subcommands? false end diff --git a/lib/cli/kit/command_help.rb b/lib/cli/kit/command_help.rb index 7ee02eff..3fa25eb9 100644 --- a/lib/cli/kit/command_help.rb +++ b/lib/cli/kit/command_help.rb @@ -5,10 +5,9 @@ module CLI module Kit module CommandHelp - extend T::Sig include Kernel # for sorbet - sig { params(args: T::Array[String], name: String).void } + #: (Array[String] args, String name) -> void def call(args, name) begin opts = self.class.opts_class @@ -36,26 +35,24 @@ def call(args, name) end # use to implement error handling - sig { params(op: T.untyped, name: String).void } + #: (untyped op, String name) -> void def invoke_wrapper(op, name) invoke(op, name) end - sig { params(op: T.untyped, name: String).void } + #: (untyped op, String name) -> void def invoke(op, name) raise(NotImplementedError, '#invoke must be implemented, or #call overridden') end class << self - extend T::Sig - - sig { params(tool_name: String).void } + #: String attr_writer :tool_name - sig { params(max_desc_length: Integer).void } + #: Integer attr_writer :max_desc_length - sig { returns(String) } + #: -> String def _tool_name unless @tool_name raise 'You must set CLI::Kit::CommandHelp.tool_name=' @@ -64,14 +61,13 @@ def _tool_name @tool_name end - sig { returns(Integer) } + #: -> Integer def _max_desc_length @max_desc_length || 80 end end module ClassMethods - extend T::Sig include Kernel # for sorbet DEFAULT_HELP_SECTIONS = [ @@ -82,7 +78,7 @@ module ClassMethods :options, ] - sig { returns(String) } + #: -> String def build_help h = (@help_sections || DEFAULT_HELP_SECTIONS).map do |section| case section @@ -103,7 +99,7 @@ def build_help CLI::UI.fmt(h) end - sig { returns(String) } + #: -> String def _command_name return @command_name if @command_name @@ -111,12 +107,12 @@ def _command_name last_camel.gsub(/([a-z])([A-Z])/, '\1-\2').downcase end - sig { returns(String) } + #: -> String def _desc @desc end - sig { returns(String) } + #: -> String def build_desc out = +"{{command:#{CommandHelp._tool_name} #{_command_name}}}" if @desc @@ -125,14 +121,15 @@ def build_desc "{{bold:#{out}}}" end - sig { returns(T.untyped) } + #: -> untyped def opts_class - T.unsafe(self).const_get(:Opts) # rubocop:disable Sorbet/ConstantsFromStrings + uself = self #: as untyped + uself.const_get(:Opts) # rubocop:disable Sorbet/ConstantsFromStrings rescue NameError Class.new(CLI::Kit::Opts) end - sig { returns(T.nilable(String)) } + #: -> String? def build_options opts = opts_class return unless opts @@ -151,7 +148,7 @@ def build_options return if @defn.options.empty? && @defn.flags.empty? - merged = T.let(@defn.options, T::Array[T.any(Args::Definition::Option, Args::Definition::Flag)]) + merged = @defn.options #: Array[(Args::Definition::Option | Args::Definition::Flag)] merged += @defn.flags merged.sort_by!(&:name) "{{bold:Options:}}\n" + merged.map do |o| @@ -179,12 +176,12 @@ def build_options end.join("\n") end - sig { params(sections: T::Array[Symbol]).void } + #: (Array[Symbol] sections) -> void def help_sections(sections) @help_sections = sections end - sig { params(command_name: String).void } + #: (String command_name) -> void def command_name(command_name) if @command_name raise(ArgumentError, "Command name already set to #{@command_name}") @@ -193,7 +190,7 @@ def command_name(command_name) @command_name = command_name end - sig { params(desc: String).void } + #: (String desc) -> void def desc(desc) # A limit of 80 characters has been chosen to fit on standard terminal configurations. `long_desc` is # available when descriptions don't fit nicely in that space. If you're using CLI::Kit for an application @@ -213,7 +210,7 @@ def desc(desc) @desc = desc end - sig { params(long_desc: String).void } + #: (String long_desc) -> void def long_desc(long_desc) if @long_desc raise(ArgumentError, 'long description already set') @@ -222,7 +219,7 @@ def long_desc(long_desc) @long_desc = long_desc end - sig { returns(String) } + #: -> String def build_usage '{{bold:Usage:}}' + case (@usage || []).size when 0 @@ -236,7 +233,7 @@ def build_usage end end - sig { returns(T.nilable(String)) } + #: -> String? def build_examples return unless @examples @@ -254,13 +251,13 @@ def build_examples end.join("\n\n") end - sig { params(usage: String).void } + #: (String usage) -> void def usage(usage) @usage ||= [] @usage << usage end - sig { params(command: String, explanation: T.nilable(String)).void } + #: (String command, String? explanation) -> void def example(command, explanation) @examples ||= [] @examples << [command, explanation] diff --git a/lib/cli/kit/command_registry.rb b/lib/cli/kit/command_registry.rb index 128656e2..d538f0e3 100644 --- a/lib/cli/kit/command_registry.rb +++ b/lib/cli/kit/command_registry.rb @@ -5,58 +5,60 @@ module CLI module Kit class CommandRegistry - extend T::Sig + #: type command_or_proc = singleton(CLI::Kit::BaseCommand) | ^() -> singleton(CLI::Kit::BaseCommand) - CommandOrProc = T.type_alias do - T.any(T.class_of(CLI::Kit::BaseCommand), T.proc.returns(T.class_of(CLI::Kit::BaseCommand))) - end - - sig { returns(T::Hash[String, CommandOrProc]) } + #: Hash[String, command_or_proc] attr_reader :commands - sig { returns(T::Hash[String, String]) } + #: Hash[String, String] attr_reader :aliases + # @interface module ContextualResolver - extend T::Sig - extend T::Helpers - interface! - - sig { abstract.returns(T::Array[String]) } - def command_names; end + # @abstract + #: -> Array[String] + def command_names + raise(NotImplementedError) + end - sig { abstract.returns(T::Hash[String, String]) } - def aliases; end + # @abstract + #: -> Hash[String, String] + def aliases + raise(NotImplementedError) + end - sig { abstract.params(_name: String).returns(T.class_of(CLI::Kit::BaseCommand)) } - def command_class(_name); end + # @abstract + #: (String) -> singleton(CLI::Kit::BaseCommand) + def command_class(_name) + raise(NotImplementedError) + end end module NullContextualResolver - extend T::Sig extend ContextualResolver class << self - extend T::Sig - - sig { override.returns(T::Array[String]) } + # @override + #: -> Array[String] def command_names [] end - sig { override.returns(T::Hash[String, String]) } + # @override + #: -> Hash[String, String] def aliases {} end - sig { override.params(_name: String).returns(T.class_of(CLI::Kit::BaseCommand)) } + # @override + #: (String _name) -> singleton(CLI::Kit::BaseCommand) def command_class(_name) raise(CLI::Kit::Abort, 'Cannot be called on the NullContextualResolver since command_names is empty') end end end - sig { params(default: String, contextual_resolver: ContextualResolver).void } + #: (default: String, ?contextual_resolver: ContextualResolver) -> void def initialize(default:, contextual_resolver: NullContextualResolver) @commands = {} @aliases = {} @@ -64,47 +66,49 @@ def initialize(default:, contextual_resolver: NullContextualResolver) @contextual_resolver = contextual_resolver end - sig { returns(T::Hash[String, T.class_of(CLI::Kit::BaseCommand)]) } + #: -> Hash[String, singleton(CLI::Kit::BaseCommand)] def resolved_commands @commands.each_with_object({}) do |(k, v), a| a[k] = resolve_class(v) end end - sig { params(const: CommandOrProc, name: String).void } + #: (command_or_proc const, String name) -> void def add(const, name) commands[name] = const end - sig { params(name: T.nilable(String)).returns([T.nilable(T.class_of(CLI::Kit::BaseCommand)), String]) } + #: (String? name) -> [singleton(CLI::Kit::BaseCommand)?, String] def lookup_command(name) name = @default if name.to_s.empty? - resolve_command(T.must(name)) + resolve_command( + name, #: as !nil + ) end - sig { params(from: String, to: String).void } + #: (String from, String to) -> void def add_alias(from, to) aliases[from] = to unless aliases[from] end - sig { returns(T::Array[String]) } + #: -> Array[String] def command_names @contextual_resolver.command_names + commands.keys end - sig { params(name: String).returns(T::Boolean) } + #: (String name) -> bool def exist?(name) !resolve_command(name).first.nil? end private - sig { params(name: String).returns(String) } + #: (String name) -> String def resolve_alias(name) aliases[name] || @contextual_resolver.aliases.fetch(name, name) end - sig { params(name: String).returns([T.nilable(T.class_of(CLI::Kit::BaseCommand)), String]) } + #: (String name) -> [singleton(CLI::Kit::BaseCommand)?, String] def resolve_command(name) name = resolve_alias(name) resolve_global_command(name) || @@ -112,7 +116,7 @@ def resolve_command(name) [nil, name] end - sig { params(name: String).returns(T.nilable([T.class_of(CLI::Kit::BaseCommand), String])) } + #: (String name) -> [singleton(CLI::Kit::BaseCommand), String]? def resolve_global_command(name) klass = resolve_class(commands.fetch(name, nil)) return unless klass @@ -122,7 +126,7 @@ def resolve_global_command(name) nil end - sig { params(name: String).returns(T.nilable([T.class_of(CLI::Kit::BaseCommand), String])) } + #: (String name) -> [singleton(CLI::Kit::BaseCommand), String]? def resolve_contextual_command(name) found = @contextual_resolver.command_names.include?(name) return unless found @@ -130,7 +134,7 @@ def resolve_contextual_command(name) [@contextual_resolver.command_class(name), name] end - sig { params(class_or_proc: T.nilable(CommandOrProc)).returns(T.nilable(T.class_of(CLI::Kit::BaseCommand))) } + #: (command_or_proc? class_or_proc) -> singleton(CLI::Kit::BaseCommand)? def resolve_class(class_or_proc) case class_or_proc when nil diff --git a/lib/cli/kit/config.rb b/lib/cli/kit/config.rb index adceed16..73124c88 100644 --- a/lib/cli/kit/config.rb +++ b/lib/cli/kit/config.rb @@ -6,11 +6,9 @@ module CLI module Kit class Config - extend T::Sig - XDG_CONFIG_HOME = 'XDG_CONFIG_HOME' - sig { params(tool_name: String).void } + #: (tool_name: String) -> void def initialize(tool_name:) @tool_name = tool_name end @@ -28,13 +26,13 @@ def initialize(tool_name:) # #### Example Usage # `config.get('name.of.config')` # - sig { params(section: String, name: String, default: T.nilable(String)).returns(T.nilable(String)) } + #: (String section, String name, ?default: String?) -> String? def get(section, name, default: nil) all_configs.dig("[#{section}]", name) || default end # Coalesce and enforce the value of a config to a boolean - sig { params(section: String, name: String, default: T.nilable(T::Boolean)).returns(T.nilable(T::Boolean)) } + #: (String section, String name, ?default: bool?) -> bool? def get_bool(section, name, default: false) case get(section, name) when 'true' @@ -58,14 +56,15 @@ def get_bool(section, name, default: false) # #### Example Usage # `config.set('section', 'name.of.config', 'value')` # - sig { params(section: String, name: String, value: T.nilable(T.any(String, T::Boolean))).void } + #: (String section, String name, (String | bool)? value) -> void def set(section, name, value) all_configs["[#{section}]"] ||= {} + section = all_configs["[#{section}]"] #: as !nil case value when nil - T.must(all_configs["[#{section}]"]).delete(name) + section.delete(name) else - T.must(all_configs["[#{section}]"])[name] = value.to_s + section[name] = value.to_s end write_config end @@ -79,7 +78,7 @@ def set(section, name, value) # #### Example Usage # `config.unset('section', 'name.of.config')` # - sig { params(section: String, name: String).void } + #: (String section, String name) -> void def unset(section, name) set(section, name, nil) end @@ -92,12 +91,12 @@ def unset(section, name) # #### Example Usage # `config.get_section('section')` # - sig { params(section: String).returns(T::Hash[String, String]) } + #: (String section) -> Hash[String, String] def get_section(section) (all_configs["[#{section}]"] || {}).dup end - sig { returns(String) } + #: -> String def to_s ini.to_s end @@ -107,7 +106,7 @@ def to_s # if ENV['XDG_CONFIG_HOME'] is not set, we default to ~/.config, e.g.: # ~/.config/tool/config # - sig { returns(String) } + #: -> String def file config_home = ENV.fetch(XDG_CONFIG_HOME, '~/.config') File.expand_path(File.join(@tool_name, 'config'), config_home) @@ -115,17 +114,17 @@ def file private - sig { returns(T::Hash[String, T::Hash[String, String]]) } + #: -> Hash[String, Hash[String, String]] def all_configs ini.ini end - sig { returns(CLI::Kit::Ini) } + #: -> CLI::Kit::Ini def ini @ini ||= CLI::Kit::Ini.new(file).tap(&:parse) end - sig { void } + #: -> void def write_config all_configs.each do |section, sub_config| all_configs.delete(section) if sub_config.empty? diff --git a/lib/cli/kit/core_ext.rb b/lib/cli/kit/core_ext.rb index ab37b030..6133a0b7 100644 --- a/lib/cli/kit/core_ext.rb +++ b/lib/cli/kit/core_ext.rb @@ -2,28 +2,26 @@ # frozen_string_literal: true class Exception - extend(T::Sig) - # You'd think instance variables @bug and @silent would work here. They # don't. I'm not sure why. If you, the reader, want to take some time to # figure it out, go ahead and refactor to that. - sig { returns(T::Boolean) } + #: -> bool def bug? true end - sig { returns(T::Boolean) } + #: -> bool def silent? false end - sig { params(bug: T::Boolean).void } + #: (?bool bug) -> void def bug!(bug = true) singleton_class.define_method(:bug?) { bug } end - sig { params(silent: T::Boolean).void } + #: (?bool silent) -> void def silent!(silent = true) singleton_class.define_method(:silent?) { silent } end diff --git a/lib/cli/kit/error_handler.rb b/lib/cli/kit/error_handler.rb index a591b17f..1a987747 100644 --- a/lib/cli/kit/error_handler.rb +++ b/lib/cli/kit/error_handler.rb @@ -6,23 +6,12 @@ module CLI module Kit class ErrorHandler - extend T::Sig + #: type exception_reporter_or_proc = singleton(ExceptionReporter) | ^() -> singleton(ExceptionReporter) - ExceptionReporterOrProc = T.type_alias do - T.any(T.class_of(ExceptionReporter), T.proc.returns(T.class_of(ExceptionReporter))) - end - - sig { params(override_exception_handler: T.proc.params(arg0: Exception).returns(Integer)).void } + #: ^(Exception arg0) -> Integer attr_writer :override_exception_handler - sig do - params( - log_file: T.nilable(String), - exception_reporter: ExceptionReporterOrProc, - tool_name: T.nilable(String), - dev_mode: T::Boolean, - ).void - end + #: (?log_file: String?, ?exception_reporter: exception_reporter_or_proc, ?tool_name: String?, ?dev_mode: bool) -> void def initialize(log_file: nil, exception_reporter: NullExceptionReporter, tool_name: nil, dev_mode: false) @log_file = log_file @exception_reporter_or_proc = exception_reporter @@ -30,33 +19,28 @@ def initialize(log_file: nil, exception_reporter: NullExceptionReporter, tool_na @dev_mode = dev_mode end + # @abstract class ExceptionReporter - extend T::Sig - extend T::Helpers - abstract! - class << self - extend T::Sig - - sig { abstract.params(exception: T.nilable(Exception), logs: T.nilable(String)).void } - def report(exception, logs = nil); end + # @abstract + #: (Exception?, ?String?) -> void + def report(exception, logs = nil) + raise(NotImplementedError) + end end end class NullExceptionReporter < ExceptionReporter - extend T::Sig - class << self - extend T::Sig - - sig { override.params(_exception: T.nilable(Exception), _logs: T.nilable(String)).void } + # @override + #: (Exception? _exception, ?String? _logs) -> void def report(_exception, _logs = nil) nil end end end - sig { params(block: T.proc.void).returns(Integer) } + #: { -> void } -> Integer def call(&block) # @at_exit_exception is set if handle_abort decides to submit an error. # $ERROR_INFO is set if we terminate because of a signal. @@ -64,7 +48,7 @@ def call(&block) triage_all_exceptions(&block) end - sig { params(error: T.nilable(Exception)).void } + #: (Exception? error) -> void def report_exception(error) if (notify_with = exception_for_submission(error)) logs = nil @@ -92,7 +76,7 @@ def report_exception(error) # they're #bug? # # Returns an exit status for the program. - sig { params(block: T.proc.void).returns(Integer) } + #: { -> void } -> Integer def triage_all_exceptions(&block) begin block.call @@ -130,7 +114,7 @@ def triage_all_exceptions(&block) e.bug? ? CLI::Kit::EXIT_BUG : CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG end - sig { params(error: T.nilable(Exception)).returns(T.nilable(Exception)) } + #: (Exception? error) -> Exception? def exception_for_submission(error) # happens on normal non-error termination return if error.nil? @@ -161,14 +145,14 @@ def exception_for_submission(error) end end - sig { params(message: String).void } + #: (String message) -> void def stderr_puts(message) $stderr.puts(CLI::UI.fmt("{{red:#{message}}}")) rescue Errno::EPIPE, Errno::EIO nil end - sig { returns(T.class_of(ExceptionReporter)) } + #: -> singleton(ExceptionReporter) def exception_reporter case @exception_reporter_or_proc when Proc diff --git a/lib/cli/kit/executor.rb b/lib/cli/kit/executor.rb index 40a744c0..cc8323b8 100644 --- a/lib/cli/kit/executor.rb +++ b/lib/cli/kit/executor.rb @@ -7,15 +7,13 @@ module CLI module Kit class Executor - extend T::Sig - - sig { params(log_file: String).void } + #: (log_file: String) -> void def initialize(log_file:) FileUtils.mkpath(File.dirname(log_file)) @log_file = log_file end - sig { params(command: T.class_of(CLI::Kit::BaseCommand), command_name: String, args: T::Array[String]).void } + #: (singleton(CLI::Kit::BaseCommand) command, String command_name, Array[String] args) -> void def call(command, command_name, args) with_traps do with_logging do |id| @@ -36,10 +34,7 @@ def call(command, command_name, args) private - sig do - type_parameters(:T).params(block: T.proc.params(id: String).returns(T.type_parameter(:T))) - .returns(T.type_parameter(:T)) - end + #: [T] { (String id) -> T } -> T def with_logging(&block) CLI::UI.log_output_to(@log_file) do CLI::UI::StdoutRouter.with_id(on_streams: [CLI::UI::StdoutRouter.duplicate_output_to].compact) do |id| @@ -48,18 +43,14 @@ def with_logging(&block) end end - sig { type_parameters(:T).params(block: T.proc.returns(T.type_parameter(:T))).returns(T.type_parameter(:T)) } + #: [T] { -> T } -> T def with_traps(&block) twrap('QUIT', method(:quit_handler)) do twrap('INFO', method(:info_handler), &block) end end - sig do - type_parameters(:T) - .params(signal: String, handler: Method, block: T.proc.returns(T.type_parameter(:T))) - .returns(T.type_parameter(:T)) - end + #: [T] (String signal, Method handler) { -> T } -> T def twrap(signal, handler, &block) return yield unless Signal.list.key?(signal) @@ -78,7 +69,7 @@ def twrap(signal, handler, &block) end end - sig { params(_sig: T.untyped).void } + #: (untyped _sig) -> void def quit_handler(_sig) z = caller CLI::UI.raw do @@ -88,7 +79,7 @@ def quit_handler(_sig) exit(CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG) end - sig { params(_sig: T.untyped).void } + #: (untyped _sig) -> void def info_handler(_sig) z = caller CLI::UI.raw do diff --git a/lib/cli/kit/ini.rb b/lib/cli/kit/ini.rb index 514c6f5b..829b983f 100644 --- a/lib/cli/kit/ini.rb +++ b/lib/cli/kit/ini.rb @@ -17,14 +17,10 @@ module Kit # See the ini_test.rb file for more examples # class Ini - extend T::Sig - - sig { returns(T::Hash[String, T::Hash[String, String]]) } + #: Hash[String, Hash[String, String]] attr_accessor :ini - sig do - params(path: T.nilable(String), config: T.nilable(String), default_section: String).void - end + #: (?String? path, ?config: String?, ?default_section: String) -> void def initialize(path = nil, config: nil, default_section: '[global]') @config = if path && File.exist?(path) File.readlines(path) @@ -35,7 +31,7 @@ def initialize(path = nil, config: nil, default_section: '[global]') @current_key = default_section end - sig { returns(T::Hash[String, T::Hash[String, String]]) } + #: -> Hash[String, Hash[String, String]] def parse return @ini if @config.nil? @@ -53,19 +49,19 @@ def parse @ini end - sig { returns(String) } + #: -> String def git_format to_ini(git_format: true) end - sig { returns(String) } + #: -> String def to_s to_ini end private - sig { params(git_format: T::Boolean).returns(String) } + #: (?git_format: bool) -> String def to_ini(git_format: false) optional_tab = git_format ? "\t" : '' str = [] @@ -79,14 +75,14 @@ def to_ini(git_format: false) str.join("\n") end - sig { params(key: String, val: String).void } + #: (String key, String val) -> void def set_val(key, val) current_key = @current_key @ini[current_key] ||= {} @ini[current_key][key] = val end - sig { params(k: String).returns(T::Boolean) } + #: (String k) -> bool def section_designator?(k) k.start_with?('[') && k.end_with?(']') end diff --git a/lib/cli/kit/levenshtein.rb b/lib/cli/kit/levenshtein.rb index 9e6e5837..20530ea7 100644 --- a/lib/cli/kit/levenshtein.rb +++ b/lib/cli/kit/levenshtein.rb @@ -28,13 +28,13 @@ module CLI module Kit module Levenshtein - extend T::Sig + # This code is based directly on the Text gem implementation # Copyright (c) 2006-2013 Paul Battley, Michael Neumann, Tim Fletcher. # # Returns a value representing the "cost" of transforming str1 into str2 - sig { params(str1: String, str2: String).returns(Integer) } + #: (String str1, String str2) -> Integer def distance(str1, str2) n = str1.length m = str2.length @@ -51,10 +51,12 @@ def distance(str1, str2) j = 0 while j < m cost = char1 == str2_codepoints[j] ? 0 : 1 + a = d[j] #: as !nil + b = d[j + 1] #: as !nil x = min3( - T.must(d[j + 1]) + 1, # insertion + b + 1, i + 1, # deletion - T.must(d[j]) + cost, # substitution + a + cost, # substitution ) d[j] = i i = x @@ -74,7 +76,7 @@ def distance(str1, str2) # faster than `[a, b, c].min` and puts less GC pressure. # See https://github.com/yuki24/did_you_mean/pull/1 for a performance # benchmark. - sig { params(a: Integer, b: Integer, c: Integer).returns(Integer) } + #: (Integer a, Integer b, Integer c) -> Integer def min3(a, b, c) if a < b && a < c a diff --git a/lib/cli/kit/logger.rb b/lib/cli/kit/logger.rb index 066592ad..bc38d37d 100644 --- a/lib/cli/kit/logger.rb +++ b/lib/cli/kit/logger.rb @@ -7,15 +7,13 @@ module CLI module Kit class Logger - extend T::Sig - MAX_LOG_SIZE = 5 * 1024 * 1000 # 5MB MAX_NUM_LOGS = 10 # Constructor for CLI::Kit::Logger # # @param debug_log_file [String] path to the file where debug logs should be stored - sig { params(debug_log_file: String, env_debug_name: String).void } + #: (debug_log_file: String, ?env_debug_name: String) -> void def initialize(debug_log_file:, env_debug_name: 'DEBUG') FileUtils.mkpath(File.dirname(debug_log_file)) @debug_logger = ::Logger.new(debug_log_file, MAX_NUM_LOGS, MAX_LOG_SIZE) @@ -27,7 +25,7 @@ def initialize(debug_log_file:, env_debug_name: 'DEBUG') # # @param msg [String] the message to log # @param debug [Boolean] determines if the debug logger will receive the log (default true) - sig { params(msg: String, debug: T::Boolean).void } + #: (String msg, ?debug: bool) -> void def info(msg, debug: true) $stdout.puts CLI::UI.fmt(msg) @debug_logger.info(format_debug(msg)) if debug @@ -38,7 +36,7 @@ def info(msg, debug: true) # # @param msg [String] the message to log # @param debug [Boolean] determines if the debug logger will receive the log (default true) - sig { params(msg: String, debug: T::Boolean).void } + #: (String msg, ?debug: bool) -> void def warn(msg, debug: true) $stdout.puts CLI::UI.fmt("{{yellow:#{msg}}}") @debug_logger.warn(format_debug(msg)) if debug @@ -49,7 +47,7 @@ def warn(msg, debug: true) # # @param msg [String] the message to log # @param debug [Boolean] determines if the debug logger will receive the log (default true) - sig { params(msg: String, debug: T::Boolean).void } + #: (String msg, ?debug: bool) -> void def error(msg, debug: true) $stderr.puts CLI::UI.fmt("{{red:#{msg}}}") @debug_logger.error(format_debug(msg)) if debug @@ -60,7 +58,7 @@ def error(msg, debug: true) # # @param msg [String] the message to log # @param debug [Boolean] determines if the debug logger will receive the log (default true) - sig { params(msg: String, debug: T::Boolean).void } + #: (String msg, ?debug: bool) -> void def fatal(msg, debug: true) $stderr.puts CLI::UI.fmt("{{red:{{bold:Fatal:}} #{msg}}}") @debug_logger.fatal(format_debug(msg)) if debug @@ -70,7 +68,7 @@ def fatal(msg, debug: true) # Logs to the debug file, taking into account CLI::UI::StdoutRouter.current_id # # @param msg [String] the message to log - sig { params(msg: String).void } + #: (String msg) -> void def debug(msg) $stdout.puts CLI::UI.fmt(msg) if debug? @debug_logger.debug(format_debug(msg)) @@ -78,7 +76,7 @@ def debug(msg) private - sig { params(msg: String).returns(String) } + #: (String msg) -> String def format_debug(msg) msg = CLI::UI.fmt(msg) return msg unless CLI::UI::StdoutRouter.current_id @@ -86,7 +84,7 @@ def format_debug(msg) "[#{CLI::UI::StdoutRouter.current_id&.fetch(:id, nil)}] #{msg}" end - sig { returns(T::Boolean) } + #: -> bool def debug? val = ENV[@env_debug_name] !!val && val != '0' && val != '' diff --git a/lib/cli/kit/opts.rb b/lib/cli/kit/opts.rb index 18b50d98..d3bfd5a4 100644 --- a/lib/cli/kit/opts.rb +++ b/lib/cli/kit/opts.rb @@ -5,16 +5,11 @@ module CLI module Kit class Opts - extend T::Sig - module Mixin - extend T::Sig include Kernel module MixinClassMethods - extend T::Sig - - sig { params(included_module: Module).void } + #: (Module included_module) -> void def include(included_module) super return unless included_module.is_a?(MixinClassMethods) @@ -28,36 +23,26 @@ def method_added(method_name) # rubocop:disable Sorbet/EnforceSignatures track_method(method_name) end - sig { params(method_name: Symbol).void } + #: (Symbol method_name) -> void def track_method(method_name) @tracked_methods ||= [] @tracked_methods << method_name unless @tracked_methods.include?(method_name) end - sig { returns(T::Array[Symbol]) } + #: -> Array[Symbol] def tracked_methods @tracked_methods || [] end end class << self - extend T::Sig - - sig { params(klass: Module).void } + #: (Module klass) -> void def included(klass) klass.extend(MixinClassMethods) end end - sig do - params( - name: Symbol, - short: T.nilable(String), - long: T.nilable(String), - desc: T.nilable(String), - default: T.any(NilClass, String, T.proc.returns(String)), - ).returns(T.nilable(String)) - end + #: (?name: Symbol, ?short: String?, ?long: String?, ?desc: String?, ?default: (String | ^-> String)?) -> String? def option(name: infer_name, short: nil, long: nil, desc: nil, default: nil) unless default.nil? raise(ArgumentError, 'declare options with non-nil defaults using `option!` instead of `option`') @@ -74,15 +59,7 @@ def option(name: infer_name, short: nil, long: nil, desc: nil, default: nil) end end - sig do - params( - name: Symbol, - short: T.nilable(String), - long: T.nilable(String), - desc: T.nilable(String), - default: T.any(NilClass, String, T.proc.returns(String)), - ).returns(String) - end + #: (?name: Symbol, ?short: String?, ?long: String?, ?desc: String?, ?default: (String | ^-> String)?) -> String def option!(name: infer_name, short: nil, long: nil, desc: nil, default: nil) case @obj when Args::Definition @@ -95,15 +72,7 @@ def option!(name: infer_name, short: nil, long: nil, desc: nil, default: nil) end end - sig do - params( - name: Symbol, - short: T.nilable(String), - long: T.nilable(String), - desc: T.nilable(String), - default: T.any(T::Array[String], T.proc.returns(T::Array[String])), - ).returns(T::Array[String]) - end + #: (?name: Symbol, ?short: String?, ?long: String?, ?desc: String?, ?default: (Array[String] | ^-> Array[String])) -> Array[String] def multi_option(name: infer_name, short: nil, long: nil, desc: nil, default: []) case @obj when Args::Definition @@ -116,14 +85,7 @@ def multi_option(name: infer_name, short: nil, long: nil, desc: nil, default: [] end end - sig do - params( - name: Symbol, - short: T.nilable(String), - long: T.nilable(String), - desc: T.nilable(String), - ).returns(T::Boolean) - end + #: (?name: Symbol, ?short: String?, ?long: String?, ?desc: String?) -> bool def flag(name: infer_name, short: nil, long: nil, desc: nil) case @obj when Args::Definition @@ -134,7 +96,7 @@ def flag(name: infer_name, short: nil, long: nil, desc: nil) end end - sig { params(name: Symbol, desc: T.nilable(String)).returns(String) } + #: (?name: Symbol, ?desc: String?) -> String def position!(name: infer_name, desc: nil) case @obj when Args::Definition @@ -145,18 +107,7 @@ def position!(name: infer_name, desc: nil) end end - sig do - params( - name: Symbol, - desc: T.nilable(String), - default: T.any(NilClass, String, T.proc.returns(String)), - skip: T.any( - NilClass, - T.proc.returns(T::Boolean), - T.proc.params(arg0: String).returns(T::Boolean), - ), - ).returns(T.nilable(String)) - end + #: (?name: Symbol, ?desc: String?, ?default: (String | ^-> String)?, ?skip: (^-> bool | ^(String arg0) -> bool)?) -> String? def position(name: infer_name, desc: nil, default: nil, skip: nil) case @obj when Args::Definition @@ -167,7 +118,7 @@ def position(name: infer_name, desc: nil, default: nil, skip: nil) end end - sig { params(name: Symbol, desc: T.nilable(String)).returns(T::Array[String]) } + #: (?name: Symbol, ?desc: String?) -> Array[String] def rest(name: infer_name, desc: nil) case @obj when Args::Definition @@ -180,14 +131,14 @@ def rest(name: infer_name, desc: nil) private - sig { params(label: T.nilable(String)).returns(T.nilable(Symbol)) } + #: (String? label) -> Symbol? def symbolize(label) return if label.nil? label.split('#').last&.to_sym end - sig { returns(Symbol) } + #: -> Symbol def infer_name to_skip = 1 Kernel.caller_locations.each do |loc| @@ -197,7 +148,7 @@ def infer_name to_skip -= 1 next end - return T.must(symbolize(loc.label)) + return symbolize(loc.label) #: as !nil end raise(ArgumentError, 'could not infer name') end @@ -206,24 +157,18 @@ def infer_name DEFAULT_OPTIONS = [:helpflag] - sig { returns(T::Boolean) } + #: -> bool def helpflag flag(name: :help, short: '-h', long: '--help', desc: 'Show this help message') end - sig { returns(T::Array[String]) } + #: -> Array[String] def unparsed obj = assert_result! obj.unparsed end - sig do - params( - block: T.nilable( - T.proc.params(arg0: Symbol, arg1: T.nilable(String)).void, - ), - ).returns(T.untyped) - end + #: ?{ (Symbol arg0, String? arg1) -> void } -> untyped def each_option(&block) return enum_for(:each_option) unless block_given? @@ -235,13 +180,7 @@ def each_option(&block) end end - sig do - params( - block: T.nilable( - T.proc.params(arg0: Symbol, arg1: T::Boolean).void, - ), - ).returns(T.untyped) - end + #: ?{ (Symbol arg0, bool arg1) -> void } -> untyped def each_flag(&block) return enum_for(:each_flag) unless block_given? @@ -253,7 +192,7 @@ def each_flag(&block) end end - sig { params(name: String).returns(T.nilable(T.any(String, T::Boolean))) } + #: (String name) -> (String | bool)? def [](name) obj = assert_result! if obj.opt.respond_to?(name) @@ -263,7 +202,7 @@ def [](name) end end - sig { params(name: String).returns(T.nilable(String)) } + #: (String name) -> String? def lookup_option(name) obj = assert_result! obj.opt.send(name) @@ -272,7 +211,7 @@ def lookup_option(name) nil end - sig { params(name: String).returns(T::Boolean) } + #: (String name) -> bool def lookup_flag(name) obj = assert_result! obj.flag.send(name) @@ -280,17 +219,18 @@ def lookup_flag(name) false end - sig { returns(Args::Evaluation) } + #: -> Args::Evaluation def assert_result! raise(NotImplementedError, 'not implemented') if @obj.is_a?(Args::Definition) @obj end - sig { params(defn: Args::Definition).void } + #: (Args::Definition defn) -> void def define!(defn) @obj = defn - T.cast(self.class, Mixin::MixinClassMethods).tracked_methods.each do |m| + klass = self.class #: as Mixin::MixinClassMethods + klass.tracked_methods.each do |m| send(m) end DEFAULT_OPTIONS.each do |m| @@ -298,7 +238,7 @@ def define!(defn) end end - sig { params(ev: Args::Evaluation).void } + #: (Args::Evaluation ev) -> void def evaluate!(ev) @obj = ev ev.resolve_positions! diff --git a/lib/cli/kit/parse_args.rb b/lib/cli/kit/parse_args.rb index 3b525af3..9aac5216 100644 --- a/lib/cli/kit/parse_args.rb +++ b/lib/cli/kit/parse_args.rb @@ -6,18 +6,12 @@ module ParseArgs # because sorbet type-checking takes the pedantic route that module doesn't include Kernel, therefore # this is necessary (even tho it's ~probably fine~) include Kernel - extend T::Sig - # T.untyped is used in two places. The interpretation of dynamic values from the provided `opts` + # untyped is used in two places. The interpretation of dynamic values from the provided `opts` # and the resulting args[:opts] is pretty broad. There seems to be minimal value in expressing a - # tighter subset of T.untyped. + # tighter subset of untyped. - sig do - params( - args: T.any(Array, String), - opts_defn: T::Hash[Symbol, T::Array[T.untyped]], - ).returns(T::Hash[Symbol, T.untyped]) - end + #: ((Array | String) args, Hash[Symbol, Array[untyped]] opts_defn) -> Hash[Symbol, untyped] def parse_args(args, opts_defn) start_opts, parser_config = opts_defn.reduce([{}, []]) do |(ini, pcfg), (n, cfg)| (vals, desc, short, klass) = cfg @@ -38,7 +32,8 @@ def parse_args(args, opts_defn) long = "--#{n.to_s.tr("_", "-")}" + (mark.nil? ? '' : " #{mark}") opt_args = klass.nil? ? [short, long, desc] : [short, long, klass, desc] - T.unsafe(opt_p).on(*opt_args) do |v| + unsafe_opt_p = opt_p #: as untyped + unsafe_opt_p.on(*opt_args) do |v| acc_opts[n] = if acc_opts.key?(n) Array(acc_opts[n]) + Array(v || def_val) else diff --git a/lib/cli/kit/resolver.rb b/lib/cli/kit/resolver.rb index 03cc8a37..3c54a4b0 100644 --- a/lib/cli/kit/resolver.rb +++ b/lib/cli/kit/resolver.rb @@ -5,15 +5,13 @@ module CLI module Kit class Resolver - extend T::Sig - - sig { params(tool_name: String, command_registry: CLI::Kit::CommandRegistry).void } + #: (tool_name: String, command_registry: CLI::Kit::CommandRegistry) -> void def initialize(tool_name:, command_registry:) @tool_name = tool_name @command_registry = command_registry end - sig { params(args: T::Array[String]).returns([T.class_of(CLI::Kit::BaseCommand), String, T::Array[String]]) } + #: (Array[String] args) -> [singleton(CLI::Kit::BaseCommand), String, Array[String]] def call(args) args = args.dup command_name = args.shift @@ -30,7 +28,7 @@ def call(args) private - sig { params(name: T.nilable(String)).void } + #: (String? name) -> void def command_not_found(name) CLI::UI::Frame.open('Command not found', color: :red, timing: false) do $stderr.puts(CLI::UI.fmt("{{command:#{@tool_name} #{name}}} was not found")) @@ -59,7 +57,7 @@ def command_not_found(name) end end - sig { returns(T::Array[String]) } + #: -> Array[String] def commands_and_aliases @command_registry.command_names + @command_registry.aliases.keys end diff --git a/lib/cli/kit/sorbet_runtime_stub.rb b/lib/cli/kit/sorbet_runtime_stub.rb deleted file mode 100644 index 1e61459e..00000000 --- a/lib/cli/kit/sorbet_runtime_stub.rb +++ /dev/null @@ -1,154 +0,0 @@ -# typed: ignore -# frozen_string_literal: true - -module T - class << self - def absurd(value); end - def all(type_a, type_b, *types); end - def any(type_a, type_b, *types); end - def attached_class; end - def class_of(klass); end - def enum(values); end - def nilable(type); end - def noreturn; end - def self_type; end - def type_alias(type = nil, &_blk); end - def type_parameter(name); end - def untyped; end - - def assert_type!(value, _type, _checked: true) - value - end - - def cast(value, _type, _checked: true) - value - end - - def let(value, _type, _checked: true) - value - end - - def must(arg, _msg = nil) - arg - end - - def proc - T::Proc.new - end - - def reveal_type(value) - value - end - - def unsafe(value) - value - end - end - - module Sig - def sig(arg0 = nil, &blk); end - end - - module Helpers - def abstract!; end - def interface!; end - def final!; end - def sealed!; end - def mixes_in_class_methods(mod); end - end - - module Generic - include(T::Helpers) - - def type_parameters(*params); end - def type_member(variance = :invariant, fixed: nil, lower: nil, upper: BasicObject); end - def type_template(variance = :invariant, fixed: nil, lower: nil, upper: BasicObject); end - - def [](*types) - self - end - end - - module Array - class << self - def [](type); end - end - end - - Boolean = Object.new.freeze - - module Configuration - class << self - def call_validation_error_handler(signature, opts); end - def call_validation_error_handler=(value); end - def default_checked_level=(default_checked_level); end - def enable_checking_for_sigs_marked_checked_tests; end - def enable_final_checks_on_hooks; end - def enable_legacy_t_enum_migration_mode; end - def reset_final_checks_on_hooks; end - def hard_assert_handler(str, extra); end - def hard_assert_handler=(value); end - def inline_type_error_handler(error); end - def inline_type_error_handler=(value); end - def log_info_handler(str, extra); end - def log_info_handler=(value); end - def scalar_types; end - def scalar_types=(values); end - def sealed_violation_whitelist; end - def sealed_violation_whitelist=(sealed_violation_whitelist); end - def sig_builder_error_handler=(value); end - def sig_validation_error_handler(error, opts); end - def sig_validation_error_handler=(value); end - def soft_assert_handler(str, extra); end - def soft_assert_handler=(value); end - end - end - - module Enumerable - class << self - def [](type); end - end - end - - module Enumerator - class << self - def [](type); end - end - end - - module Hash - class << self - def [](keys, values); end - end - end - - class Proc - def bind(*_) - self - end - - def params(*_param) - self - end - - def void - self - end - - def returns(_type) - self - end - end - - module Range - class << self - def [](type); end - end - end - - module Set - class << self - def [](type); end - end - end -end diff --git a/lib/cli/kit/support/test_helper.rb b/lib/cli/kit/support/test_helper.rb index dd800d87..f4a7757a 100644 --- a/lib/cli/kit/support/test_helper.rb +++ b/lib/cli/kit/support/test_helper.rb @@ -14,7 +14,8 @@ def assert_all_commands_run(should_raise: true) CLI::Kit::System.reset! # this is in minitest, but sorbet doesn't know that. probably we # could structure this better. - T.unsafe(self).assert(false, errors) if should_raise && !errors.nil? + uself = self #: as untyped + uself.assert(false, errors) if should_raise && !errors.nil? errors end @@ -67,7 +68,8 @@ def system(cmd, *a, sudo: false, env: {}, stdin: nil, **kwargs) # Otherwise handle the command if expected_command[:allow] - T.unsafe(self).original_system(*a, sudo: sudo, env: env, **kwargs) + uself = self #: as untyped + uself.original_system(*a, sudo: sudo, env: env, **kwargs) else FakeSuccess.new(expected_command[:success]) end @@ -83,7 +85,8 @@ def capture2(cmd, *a, sudo: false, env: {}, **kwargs) # Otherwise handle the command if expected_command[:allow] - T.unsafe(self).original_capture2(*a, sudo: sudo, env: env, **kwargs) + uself = self #: as untyped + uself.original_capture2(*a, sudo: sudo, env: env, **kwargs) else [ expected_command[:stdout], @@ -102,7 +105,8 @@ def capture2e(cmd, *a, sudo: false, env: {}, **kwargs) # Otherwise handle the command if expected_command[:allow] - T.unsafe(self).original_capture2e(*a, sudo: sudo, env: env, **kwargs) + uself = self #: as untyped + uself.original_capture2e(*a, sudo: sudo, env: env, **kwargs) else [ expected_command[:stdout], @@ -121,7 +125,8 @@ def capture3(cmd, *a, sudo: false, env: {}, **kwargs) # Otherwise handle the command if expected_command[:allow] - T.unsafe(self).original_capture3(*a, sudo: sudo, env: env, **kwargs) + uself = self #: as untyped + uself.original_capture3(*a, sudo: sudo, env: env, **kwargs) else [ expected_command[:stdout], diff --git a/lib/cli/kit/system.rb b/lib/cli/kit/system.rb index 93b0b6c7..822e9781 100644 --- a/lib/cli/kit/system.rb +++ b/lib/cli/kit/system.rb @@ -9,8 +9,6 @@ module Kit module System SUDO_PROMPT = CLI::UI.fmt('{{info:(sudo)}} Password: ') class << self - extend T::Sig - # Ask for sudo access with a message explaning the need for it # Will make subsequent commands capable of running with sudo for a period of time # @@ -20,7 +18,7 @@ class << self # #### Usage # `ctx.sudo_reason("We need to do a thing")` # - sig { params(msg: String).void } + #: (String msg) -> void def sudo_reason(msg) # See if sudo has a cached password %x(env SUDO_ASKPASS=/usr/bin/false sudo -A true > /dev/null 2>&1) @@ -48,16 +46,7 @@ def sudo_reason(msg) # #### Usage # `out, stat = CLI::Kit::System.capture2('ls', 'a_folder')` # - sig do - params( - cmd: String, - args: String, - sudo: T.any(T::Boolean, String), - env: T::Hash[String, T.nilable(String)], - kwargs: T.untyped, - ) - .returns([String, Process::Status]) - end + #: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) -> [String, Process::Status] def capture2(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture2) end @@ -79,16 +68,7 @@ def capture2(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) # #### Usage # `out_and_err, stat = CLI::Kit::System.capture2e('ls', 'a_folder')` # - sig do - params( - cmd: String, - args: String, - sudo: T.any(T::Boolean, String), - env: T::Hash[String, T.nilable(String)], - kwargs: T.untyped, - ) - .returns([String, Process::Status]) - end + #: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) -> [String, Process::Status] def capture2e(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture2e) end @@ -111,70 +91,22 @@ def capture2e(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) # #### Usage # `out, err, stat = CLI::Kit::System.capture3('ls', 'a_folder')` # - sig do - params( - cmd: String, - args: String, - sudo: T.any(T::Boolean, String), - env: T::Hash[String, T.nilable(String)], - kwargs: T.untyped, - ) - .returns([String, String, Process::Status]) - end + #: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) -> [String, String, Process::Status] def capture3(cmd, *args, sudo: false, env: ENV.to_h, **kwargs) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :capture3) end - sig do - params( - cmd: String, - args: String, - sudo: T.any(T::Boolean, String), - env: T::Hash[String, T.nilable(String)], - kwargs: T.untyped, - block: T.nilable( - T.proc.params(stdin: IO, stdout: IO, wait_thr: Process::Waiter) - .returns([IO, IO, Process::Waiter]), - ), - ) - .returns([IO, IO, Process::Waiter]) - end + #: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) ?{ (IO stdin, IO stdout, Process::Waiter wait_thr) -> [IO, IO, Process::Waiter] } -> [IO, IO, Process::Waiter] def popen2(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :popen2, &block) end - sig do - params( - cmd: String, - args: String, - sudo: T.any(T::Boolean, String), - env: T::Hash[String, T.nilable(String)], - kwargs: T.untyped, - block: T.nilable( - T.proc.params(stdin: IO, stdout: IO, wait_thr: Process::Waiter) - .returns([IO, IO, Process::Waiter]), - ), - ) - .returns([IO, IO, Process::Waiter]) - end + #: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) ?{ (IO stdin, IO stdout, Process::Waiter wait_thr) -> [IO, IO, Process::Waiter] } -> [IO, IO, Process::Waiter] def popen2e(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :popen2e, &block) end - sig do - params( - cmd: String, - args: String, - sudo: T.any(T::Boolean, String), - env: T::Hash[String, T.nilable(String)], - kwargs: T.untyped, - block: T.nilable( - T.proc.params(stdin: IO, stdout: IO, stderr: IO, wait_thr: Process::Waiter) - .returns([IO, IO, IO, Process::Waiter]), - ), - ) - .returns([IO, IO, IO, Process::Waiter]) - end + #: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], **untyped kwargs) ?{ (IO stdin, IO stdout, IO stderr, Process::Waiter wait_thr) -> [IO, IO, IO, Process::Waiter] } -> [IO, IO, IO, Process::Waiter] def popen3(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) delegate_open3(cmd, args, kwargs, sudo: sudo, env: env, method: :popen3, &block) end @@ -194,18 +126,7 @@ def popen3(cmd, *args, sudo: false, env: ENV.to_h, **kwargs, &block) # #### Usage # `stat = CLI::Kit::System.system('ls', 'a_folder')` # - sig do - params( - cmd: String, - args: String, - sudo: T.any(T::Boolean, String), - env: T::Hash[String, T.nilable(String)], - stdin: T.nilable(T.any(IO, String, Integer, Symbol)), - kwargs: T.untyped, - block: T.nilable(T.proc.params(out: String, err: String).void), - ) - .returns(Process::Status) - end + #: (String cmd, *String args, ?sudo: (String | bool), ?env: Hash[String, String?], ?stdin: (IO | String | Integer | Symbol)?, **untyped kwargs) ?{ (String out, String err) -> void } -> Process::Status def system(cmd, *args, sudo: false, env: ENV.to_h, stdin: nil, **kwargs, &block) cmd, args = apply_sudo(cmd, args, sudo) @@ -219,7 +140,8 @@ def system(cmd, *args, sudo: false, env: ENV.to_h, stdin: nil, **kwargs, &block) STDIN end cmd, args = resolve_path(cmd, args, env) - pid = T.unsafe(Process).spawn(env, cmd, *args, 0 => in_stream, :out => out_w, :err => err_w, **kwargs) + process = Process #: as untyped + pid = process.spawn(env, cmd, *args, 0 => in_stream, :out => out_w, :err => err_w, **kwargs) out_w.close err_w.close @@ -260,15 +182,16 @@ def system(cmd, *args, sudo: false, env: ENV.to_h, stdin: nil, **kwargs, &block) # Split off trailing partial UTF-8 Characters. UTF-8 Multibyte characters start with a 11xxxxxx byte that tells # how many following bytes are part of this character, followed by some number of 10xxxxxx bytes. This simple # algorithm will split off a whole trailing multi-byte character. - sig { params(data: String).returns([String, String]) } + #: (String data) -> [String, String] def split_partial_characters(data) - last_byte = T.must(data.getbyte(-1)) + last_byte = data.getbyte(-1) #: as !nil return [data, ''] if (last_byte & 0b1000_0000).zero? # UTF-8 is up to 4 characters per rune, so we could never want to trim more than that, and we want to avoid # allocating an array for the whole of data with bytes min_bound = -[4, data.bytesize].min - final_bytes = T.must(data.byteslice(min_bound..-1)).bytes + fb = data.byteslice(min_bound..-1) #: as !nil + final_bytes = fb.bytes partial_character_sub_index = final_bytes.rindex { |byte| byte & 0b1100_0000 == 0b1100_0000 } # Bail out for non UTF-8 @@ -293,10 +216,13 @@ def split_partial_characters(data) partial_character_index = min_bound + partial_character_sub_index - [T.must(data.byteslice(0...partial_character_index)), T.must(data.byteslice(partial_character_index..-1))] + [ + data.byteslice(0...partial_character_index), #: as !nil + data.byteslice(partial_character_index..-1), #: as !nil + ] end - sig { returns(Symbol) } + #: -> Symbol def os return :mac if /darwin/.match(RUBY_PLATFORM) return :linux if /linux/.match(RUBY_PLATFORM) @@ -305,7 +231,7 @@ def os raise "Could not determine OS from platform #{RUBY_PLATFORM}" end - sig { params(cmd: String, env: T::Hash[String, T.nilable(String)]).returns(T.nilable(String)) } + #: (String cmd, Hash[String, String?] env) -> String? def which(cmd, env) exts = os == :windows ? (env['PATHEXT'] || 'exe').split(';') : [''] (env['PATH'] || '').split(File::PATH_SEPARATOR).each do |path| @@ -320,10 +246,7 @@ def which(cmd, env) private - sig do - params(cmd: String, args: T::Array[String], sudo: T.any(T::Boolean, String)) - .returns([String, T::Array[String]]) - end + #: (String cmd, Array[String] args, (String | bool) sudo) -> [String, Array[String]] def apply_sudo(cmd, args, sudo) return [cmd, args] if !sudo || Process.uid.zero? @@ -331,21 +254,12 @@ def apply_sudo(cmd, args, sudo) ['sudo', args.unshift('-E', '-S', '-p', SUDO_PROMPT, '--', cmd)] end - sig do - params( - cmd: String, - args: T::Array[String], - kwargs: T::Hash[Symbol, T.untyped], - sudo: T.any(T::Boolean, String), - env: T::Hash[String, T.nilable(String)], - method: Symbol, - block: T.untyped, - ).returns(T.untyped) - end + #: (String cmd, Array[String] args, Hash[Symbol, untyped] kwargs, ?sudo: (String | bool), ?env: Hash[String, String?], ?method: Symbol) ?{ (?) -> untyped } -> untyped def delegate_open3(cmd, args, kwargs, sudo: raise, env: raise, method: raise, &block) cmd, args = apply_sudo(cmd, args, sudo) cmd, args = resolve_path(cmd, args, env) - T.unsafe(Open3).send(method, env, cmd, *args, **kwargs, &block) + open3 = Open3 #: as untyped + open3.send(method, env, cmd, *args, **kwargs, &block) rescue Errno::EINTR raise(Errno::EINTR, "command interrupted: #{cmd} #{args.join(" ")}") end @@ -359,10 +273,7 @@ def delegate_open3(cmd, args, kwargs, sudo: raise, env: raise, method: raise, &b # project. # # See https://github.com/Shopify/dev/pull/625 for more details. - sig do - params(cmd: String, args: T::Array[String], env: T::Hash[String, T.nilable(String)]) - .returns([String, T::Array[String]]) - end + #: (String cmd, Array[String] args, Hash[String, String?] env) -> [String, Array[String]] def resolve_path(cmd, args, env) # If only one argument was provided, make sure it's interpreted by a shell. if args.empty? diff --git a/lib/cli/kit/util.rb b/lib/cli/kit/util.rb index e5d38b66..48e6c8c0 100644 --- a/lib/cli/kit/util.rb +++ b/lib/cli/kit/util.rb @@ -6,21 +6,17 @@ module CLI module Kit module Util class << self - extend T::Sig # # Converts an integer representing bytes into a human readable format # - sig { params(bytes: Integer, precision: Integer, space: T::Boolean).returns(String) } + #: (Integer bytes, ?precision: Integer, ?space: bool) -> String def to_filesize(bytes, precision: 2, space: false) to_si_scale(bytes, 'B', precision: precision, space: space, factor: 1024) end # Converts a number to a human readable format on the SI scale # - sig do - params(number: Numeric, unit: String, factor: Integer, precision: Integer, space: T::Boolean) - .returns(String) - end + #: (Numeric number, ?String unit, ?factor: Integer, ?precision: Integer, ?space: bool) -> String def to_si_scale(number, unit = '', factor: 1000, precision: 2, space: false) raise ArgumentError, 'factor should only be 1000 or 1024' unless [1000, 1024].include?(factor) @@ -37,11 +33,11 @@ def to_si_scale(number, unit = '', factor: 1000, precision: 2, space: false) if number < 1 index = [-scale - 1, small_scale.length].min scale = -(index + 1) - prefix = T.must(small_scale[index]) + prefix = small_scale[index] #: as !nil else index = [scale - 1, big_scale.length].min scale = index + 1 - prefix = T.must(big_scale[index]) + prefix = big_scale[index] #: as !nil end end @@ -62,10 +58,7 @@ def to_si_scale(number, unit = '', factor: 1000, precision: 2, space: false) # Dir.chdir, when invoked in block form, complains when we call chdir # again recursively. There's no apparent good reason for this, so we # simply implement our own block form of Dir.chdir here. - sig do - type_parameters(:T).params(dir: String, block: T.proc.returns(T.type_parameter(:T))) - .returns(T.type_parameter(:T)) - end + #: [T] (String dir) { -> T } -> T def with_dir(dir, &block) prev = Dir.pwd begin @@ -85,44 +78,35 @@ def with_dir(dir, &block) # end.retry_after(ExpectedError) do # costly_prep() # end - sig do - type_parameters(:T).params(block_that_might_raise: T.proc.returns(T.type_parameter(:T))) - .returns(Retrier[T.type_parameter(:T)]) - end + #: [T] { -> T } -> Retrier[T] def begin(&block_that_might_raise) Retrier.new(block_that_might_raise) end end + #: [BlockReturnType] class Retrier - extend T::Sig - extend T::Generic - - BlockReturnType = type_member - - sig { params(block_that_might_raise: T.proc.returns(BlockReturnType)).void } + #: (^-> BlockReturnType block_that_might_raise) -> void def initialize(block_that_might_raise) @block_that_might_raise = block_that_might_raise end - sig do - params( - exception: T.class_of(Exception), - retries: Integer, - before_retry: T.nilable(T.proc.params(e: Exception).void), - ).returns(BlockReturnType) - end + #: (?singleton(Exception) exception, ?retries: Integer) ?{ (Exception e) -> void } -> BlockReturnType def retry_after(exception = StandardError, retries: 1, &before_retry) @block_that_might_raise.call rescue exception => e raise if (retries -= 1) < 0 if before_retry + # rubocop:disable Style/IdenticalConditionalBranches if before_retry.arity == 0 - T.cast(before_retry, T.proc.void).call + prc = before_retry #: as ^() -> void + prc.call else - T.cast(before_retry, T.proc.params(e: Exception).void).call(e) + prc = before_retry #: as ^(Exception) -> void + prc.call(e) end + # rubocop:enable Style/IdenticalConditionalBranches end retry end diff --git a/sorbet/config b/sorbet/config index 2a3fc261..2a6b0d2d 100644 --- a/sorbet/config +++ b/sorbet/config @@ -6,3 +6,4 @@ examples/ gen/template/ --ignore vendor/ +--enable-experimental-rbs-comments diff --git a/sorbet/rbi/lib/minitest/all/minitest.rbi b/sorbet/rbi/lib/minitest/all/minitest.rbi index 7208ffd0..a86f5ed5 100644 --- a/sorbet/rbi/lib/minitest/all/minitest.rbi +++ b/sorbet/rbi/lib/minitest/all/minitest.rbi @@ -23,7 +23,7 @@ module Minitest end module Minitest::Assertions - extend T::Sig + sig { params(test: T.untyped, msg: T.nilable(String)).returns(TrueClass) } def assert(test, msg = nil); end diff --git a/sorbet/tapioca/require.rb b/sorbet/tapioca/require.rb index b8bcdb1e..fb8f62c4 100644 --- a/sorbet/tapioca/require.rb +++ b/sorbet/tapioca/require.rb @@ -5,7 +5,6 @@ require 'bundler/setup' require 'byebug' require 'cli/kit' -require 'cli/kit/sorbet_runtime_stub' require 'cli/kit/version' require 'cli/ui' require 'cli/ui/version' diff --git a/test/cli/kit/raise_test.rb b/test/cli/kit/raise_test.rb index 4fa24f78..41d58969 100644 --- a/test/cli/kit/raise_test.rb +++ b/test/cli/kit/raise_test.rb @@ -40,12 +40,12 @@ def check(bug: nil, silent: nil, message: nil, klass: nil, &block) exc = e end assert(exc) - exc = T.must(exc) + exc = exc #: as !nil assert_equal(bug, exc.bug?) unless bug.nil? assert_equal(silent, exc.silent?) unless silent.nil? assert_equal(message, exc.message) unless message.nil? assert_equal(klass, exc.class) unless klass.nil? - want_function = T.must(caller_locations(1, 1)&.first&.label) + want_function = caller_locations(1, 1)&.first&.label #: as !nil assert( exc.backtrace&.detect { |l| !l.match?(/sorbet-runtime/) }&.index(want_function), 'backtrace trimming looks wrong', diff --git a/test/test_helper.rb b/test/test_helper.rb index 222fae5d..0ddff08d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,3 @@ -require 'sorbet-runtime' unless ENV['BUNDLE_WITHOUT'] == 'typecheck' - addpath = lambda do |p| path = File.expand_path("../../#{p}", __FILE__) $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path) @@ -25,7 +23,6 @@ SimpleCov.start do # SimpleCov uses a "creative" DSL here with block rebinding. # Sorbet doesn't like it. - T.unsafe(self).add_filter('/lib/cli/kit/sorbet_runtime_stub.rb') T.unsafe(self).add_filter('/test/') T.unsafe(self).add_filter('/gen/') T.unsafe(self).add_filter('/examples/')