diff --git a/Library/Homebrew/sorbet/rbi/dsl/homebrew/cli/args.rbi b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cli/args.rbi index 1a5c5013c4be08..c88f445543e472 100644 --- a/Library/Homebrew/sorbet/rbi/dsl/homebrew/cli/args.rbi +++ b/Library/Homebrew/sorbet/rbi/dsl/homebrew/cli/args.rbi @@ -539,7 +539,7 @@ class Homebrew::CLI::Args sig { returns(T::Boolean) } def online?; end - sig { returns(T.nilable(String)) } + sig { returns(T.nilable(T::Array[String])) } def only; end sig { returns(T.nilable(T::Array[String])) } diff --git a/Library/Homebrew/sorbet/tapioca/compilers/args.rb b/Library/Homebrew/sorbet/tapioca/compilers/args.rb index 9c2d1af83204f3..5a226f1eaa35a9 100644 --- a/Library/Homebrew/sorbet/tapioca/compilers/args.rb +++ b/Library/Homebrew/sorbet/tapioca/compilers/args.rb @@ -3,11 +3,6 @@ require_relative "../../../global" -# require all the commands -["cmd", "dev-cmd"].each do |dir| - Dir[File.join(__dir__, "../../../#{dir}", "*.rb")].each { require(_1) } -end - module Tapioca module Compilers class Args < Tapioca::Dsl::Compiler @@ -25,7 +20,13 @@ class Args < Tapioca::Dsl::Compiler # rubocop:enable Style/MutableConstant sig { override.returns(T::Enumerable[T.class_of(Homebrew::CLI::Args)]) } - def self.gather_constants = [Homebrew::CLI::Args] + def self.gather_constants + # require all the commands to ensure the _arg methods are defined + ["cmd", "dev-cmd"].each do |dir| + Dir[File.join(__dir__, "../../../#{dir}", "*.rb")].each { require(_1) } + end + [Homebrew::CLI::Args] + end sig { override.void } def decorate @@ -35,18 +36,29 @@ def decorate parser = Homebrew.method(args_method_name).call comma_array_methods = comma_arrays(parser) - args = parser.instance_variable_get(:@args) - args.instance_variable_get(:@table).each do |method_name, value| + args_table(parser).each do |method_name, value| # some args are used in multiple commands (this is ok as long as they have the same type) - next if klass.nodes.any? { T.cast(_1, RBI::Method).name == method_name } || value == [] + next if klass.nodes.any? { T.cast(_1, RBI::Method).name.to_sym == method_name } return_type = get_return_type(method_name, value, comma_array_methods) - klass.create_method(method_name, return_type:) + klass.create_method(method_name.to_s, return_type:) end end end end + sig { params(parser: Homebrew::CLI::Parser).returns(T::Hash[Symbol, T.untyped]) } + def args_table(parser) + # we exclude non-args from the table, such as :named and :remaining + parser.instance_variable_get(:@args).instance_variable_get(:@table).except(:named, :remaining) + end + + sig { params(parser: Homebrew::CLI::Parser).returns(T::Array[Symbol]) } + def comma_arrays(parser) + parser.instance_variable_get(:@non_global_processed_options) + .filter_map { |k, v| parser.option_to_name(k).to_sym if v == :comma_array } + end + sig { params(method_name: Symbol, value: T.untyped, comma_array_methods: T::Array[Symbol]).returns(String) } def get_return_type(method_name, value, comma_array_methods) if comma_array_methods.include?(method_name) @@ -57,12 +69,6 @@ def get_return_type(method_name, value, comma_array_methods) "T.nilable(String)" end end - - sig { params(parser: Homebrew::CLI::Parser).returns(T::Array[Symbol]) } - def comma_arrays(parser) - parser.instance_variable_get(:@non_global_processed_options) - .filter_map { |k, v| parser.option_to_name(k).to_sym if v == :comma_array } - end end end end diff --git a/Library/Homebrew/test/sorbet/tapioca/compilers/args_spec.rb b/Library/Homebrew/test/sorbet/tapioca/compilers/args_spec.rb new file mode 100644 index 00000000000000..631a80d2c84b14 --- /dev/null +++ b/Library/Homebrew/test/sorbet/tapioca/compilers/args_spec.rb @@ -0,0 +1,94 @@ +# frozen_string_literal: true + +# require 'tapioca' +require "tapioca/dsl" +require_relative "../../../../sorbet/tapioca/compilers/args" + +RSpec.describe Tapioca::Compilers::Args do + let(:compiler) { described_class.new(Tapioca::Dsl::Pipeline.new(requested_constants: []), RBI::Tree.new, Homebrew) } + let(:list_parser) do + require "cmd/list" + Homebrew.list_args + end + # good testing candidate, bc it has multiple for each of switch, flag, and comma_array args: + let(:update_python_resources_parser) do + require "dev-cmd/update-python-resources" + Homebrew.update_python_resources_args + end + + describe "#args_table" do + it "returns a mapping of list args to default values" do + expect(compiler.args_table(list_parser)).to eq({ + "1?": false, + cask?: false, + casks?: false, + d?: false, + debug?: false, + formula?: false, + formulae?: false, + full_name?: false, + h?: false, + help?: false, + l?: false, + multiple?: false, + pinned?: false, + q?: false, + quiet?: false, + r?: false, + t?: false, + v?: false, + verbose?: false, + versions?: false, + }) + end + + it "rreturns a mapping of update-python-resources args to default values" do + expect(compiler.args_table(update_python_resources_parser)).to eq({ + d?: false, + debug?: false, + exclude_packages: nil, + extra_packages: nil, + h?: false, + help?: false, + ignore_non_pypi_packages?: false, + install_dependencies?: false, + p?: false, + package_name: nil, + print_only?: false, + q?: false, + quiet?: false, + s?: false, + silent?: false, + v?: false, + verbose?: false, + version: nil, + }) + end + end + + describe "#comma_arrays" do + it "returns an empty list when there are no comma_array args" do + expect(compiler.comma_arrays(list_parser)).to eq([]) + end + + it "returns the comma_array args when they exist" do + expect(compiler.comma_arrays(update_python_resources_parser)).to eq([:extra_packages, :exclude_packages]) + end + end + + describe "#get_return_type" do + let(:comma_arrays) { compiler.comma_arrays(update_python_resources_parser) } + + it "returns the correct type for switches" do + expect(compiler.get_return_type(:silent?, false, comma_arrays)).to eq("T::Boolean") + end + + it "returns the correct type for flags" do + expect(compiler.get_return_type(:package_name, nil, comma_arrays)).to eq("T.nilable(String)") + end + + it "returns the correct type for comma_arrays" do + expect(compiler.get_return_type(:extra_packages, nil, comma_arrays)).to eq("T.nilable(T::Array[String])") + end + end +end