diff --git a/Library/.rubocop.yml b/Library/.rubocop.yml index 3a453e8c50..83165428c6 100644 --- a/Library/.rubocop.yml +++ b/Library/.rubocop.yml @@ -279,6 +279,7 @@ Sorbet/FalseSigil: - 'Homebrew/test/**/Casks/**/*.rb' Sorbet/StrictSigil: + Enabled: true Include: - '**/*.rbi' diff --git a/Library/Homebrew/PATH.rb b/Library/Homebrew/PATH.rb index f9c347d6e8..20b83d0d36 100644 --- a/Library/Homebrew/PATH.rb +++ b/Library/Homebrew/PATH.rb @@ -1,52 +1,71 @@ -# typed: false +# typed: true # frozen_string_literal: true # Represention of a `*PATH` environment variable. # # @api private class PATH + extend T::Sig + include Enumerable extend Forwardable def_delegator :@paths, :each + # FIXME: Enable cop again when https://github.com/sorbet/sorbet/issues/3532 is fixed. + # rubocop:disable Style/MutableConstant + Element = T.type_alias { T.nilable(T.any(Pathname, String, PATH)) } + private_constant :Element + Elements = T.type_alias { T.any(Element, T::Array[Element]) } + private_constant :Elements + # rubocop:enable Style/MutableConstant + + sig { params(paths: Elements).void } def initialize(*paths) - @paths = parse(*paths) + @paths = parse(paths) end + sig { params(paths: Elements).returns(T.self_type) } def prepend(*paths) - @paths = parse(*paths, *@paths) + @paths = parse(paths + @paths) self end + sig { params(paths: Elements).returns(T.self_type) } def append(*paths) - @paths = parse(*@paths, *paths) + @paths = parse(@paths + paths) self end + sig { params(index: Integer, paths: Elements).returns(T.self_type) } def insert(index, *paths) - @paths = parse(*@paths.insert(index, *paths)) + @paths = parse(@paths.insert(index, *paths)) self end + sig { params(block: T.proc.params(arg0: String).returns(T::Boolean)).returns(T.self_type) } def select(&block) self.class.new(@paths.select(&block)) end + sig { params(block: T.proc.params(arg0: String).returns(T::Boolean)).returns(T.self_type) } def reject(&block) self.class.new(@paths.reject(&block)) end + sig { returns(T::Array[String]) } def to_ary @paths.dup.to_ary end alias to_a to_ary + sig { returns(String) } def to_str @paths.join(File::PATH_SEPARATOR) end alias to_s to_str + sig { params(other: T.untyped).returns(T::Boolean) } def ==(other) if other.respond_to?(:to_ary) && to_ary == other.to_ary true @@ -57,10 +76,12 @@ class PATH end end + sig { returns(T::Boolean) } def empty? @paths.empty? end + sig { returns(T.nilable(T.self_type)) } def existing existing_path = select(&File.method(:directory?)) # return nil instead of empty PATH, to unset environment variables @@ -69,10 +90,11 @@ class PATH private - def parse(*paths) + sig { params(paths: T::Array[Elements]).returns(T::Array[String]) } + def parse(paths) paths.flatten .compact - .flat_map { |p| Pathname.new(p).to_path.split(File::PATH_SEPARATOR) } + .flat_map { |p| Pathname(p).to_path.split(File::PATH_SEPARATOR) } .uniq end end diff --git a/Library/Homebrew/extend/os/mac/unpack_strategy/zip.rb b/Library/Homebrew/extend/os/mac/unpack_strategy/zip.rb index d90cdb21f5..b804e49f35 100644 --- a/Library/Homebrew/extend/os/mac/unpack_strategy/zip.rb +++ b/Library/Homebrew/extend/os/mac/unpack_strategy/zip.rb @@ -1,9 +1,19 @@ -# typed: false +# typed: strict # frozen_string_literal: true +require "system_command" + module UnpackStrategy class Zip module MacOSZipExtension + extend T::Sig + + include UnpackStrategy + include SystemCommand::Mixin + + using Magic + + sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) } def extract_to_dir(unpack_dir, basename:, verbose:) if merge_xattrs && contains_extended_attributes?(path) # We use ditto directly, because dot_clean has issues if the __MACOSX @@ -16,7 +26,7 @@ module UnpackStrategy end result = begin - super + T.let(super, SystemCommand::Result) rescue ErrorDuringExecution => e raise unless e.stderr.include?("End-of-central-directory signature not found.") @@ -47,6 +57,13 @@ module UnpackStrategy end end end + + private + + sig { params(path: Pathname).returns(T::Boolean) } + def contains_extended_attributes?(path) + path.zipinfo.grep(/(^__MACOSX|\._)/).any? + end end private_constant :MacOSZipExtension diff --git a/Library/Homebrew/extend/os/mac/unpack_strategy/zip.rbi b/Library/Homebrew/extend/os/mac/unpack_strategy/zip.rbi new file mode 100644 index 0000000000..ee40416382 --- /dev/null +++ b/Library/Homebrew/extend/os/mac/unpack_strategy/zip.rbi @@ -0,0 +1,9 @@ +# typed: strict + +module UnpackStrategy + class Zip + module MacOSZipExtension + include Kernel + end + end +end diff --git a/Library/Homebrew/sorbet/config b/Library/Homebrew/sorbet/config index 02fbf5ab02..024ba458ec 100644 --- a/Library/Homebrew/sorbet/config +++ b/Library/Homebrew/sorbet/config @@ -6,3 +6,6 @@ --ignore /test/.gem + +--dsl-plugins +sorbet/triggers.yml diff --git a/Library/Homebrew/sorbet/plugins/unpack_strategy_magic.rb b/Library/Homebrew/sorbet/plugins/unpack_strategy_magic.rb new file mode 100644 index 0000000000..d5c83fa8b5 --- /dev/null +++ b/Library/Homebrew/sorbet/plugins/unpack_strategy_magic.rb @@ -0,0 +1,21 @@ +# typed: strict +# frozen_string_literal: true + +source = ARGV[5] + +/\busing +Magic\b/.match(source) do |_| + puts <<-RUBY + # typed: strict + + class ::Pathname + sig { returns(String) } + def magic_number; end + + sig { returns(String) } + def file_type; end + + sig { returns(T::Array[String]) } + def zipinfo; end + end + RUBY +end diff --git a/Library/Homebrew/sorbet/triggers.yml b/Library/Homebrew/sorbet/triggers.yml new file mode 100644 index 0000000000..52bc3b2aea --- /dev/null +++ b/Library/Homebrew/sorbet/triggers.yml @@ -0,0 +1,5 @@ +ruby_extra_args: + - --disable-gems + +triggers: + using: sorbet/plugins/unpack_strategy_magic.rb diff --git a/Library/Homebrew/unpack_strategy.rb b/Library/Homebrew/unpack_strategy.rb index 774d296156..c187be8286 100644 --- a/Library/Homebrew/unpack_strategy.rb +++ b/Library/Homebrew/unpack_strategy.rb @@ -7,12 +7,16 @@ require "system_command" # # @api private module UnpackStrategy + extend T::Sig + extend T::Helpers + include SystemCommand::Mixin # Helper module for identifying the file type. module Magic # Length of the longest regex (currently Tar). MAX_MAGIC_NUMBER_LENGTH = 262 + private_constant :MAX_MAGIC_NUMBER_LENGTH refine Pathname do def magic_number @@ -125,11 +129,16 @@ module UnpackStrategy @merge_xattrs = merge_xattrs end - def extract(to: nil, basename: nil, verbose: false) + abstract! + sig { abstract.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) } + def extract_to_dir(unpack_dir, basename:, verbose:); end + private :extract_to_dir + + def extract(to: nil, basename: nil, verbose: nil) basename ||= path.basename unpack_dir = Pathname(to || Dir.pwd).expand_path unpack_dir.mkpath - extract_to_dir(unpack_dir, basename: basename, verbose: verbose) + extract_to_dir(unpack_dir, basename: Pathname(basename), verbose: verbose || false) end def extract_nestedly(to: nil, basename: nil, verbose: false, prioritise_extension: false) diff --git a/Library/Homebrew/unpack_strategy.rbi b/Library/Homebrew/unpack_strategy.rbi new file mode 100644 index 0000000000..d7550c56b2 --- /dev/null +++ b/Library/Homebrew/unpack_strategy.rbi @@ -0,0 +1,5 @@ +# typed: strict + +module UnpackStrategy + include Kernel +end diff --git a/Library/Homebrew/unpack_strategy/zip.rb b/Library/Homebrew/unpack_strategy/zip.rb index 09db337f84..ac5238d5ca 100644 --- a/Library/Homebrew/unpack_strategy/zip.rb +++ b/Library/Homebrew/unpack_strategy/zip.rb @@ -1,27 +1,31 @@ -# typed: true +# typed: strict # frozen_string_literal: true module UnpackStrategy # Strategy for unpacking ZIP archives. class Zip + extend T::Sig + include UnpackStrategy using Magic + sig { returns(T::Array[String]) } def self.extensions [".zip"] end + sig { params(path: Pathname).returns(T::Boolean) } def self.can_extract?(path) path.magic_number.match?(/\APK(\003\004|\005\006)/n) end private - def contains_extended_attributes?(path) - path.zipinfo.grep(/(^__MACOSX|\._)/).any? + sig do + override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean) + .returns(SystemCommand::Result) end - def extract_to_dir(unpack_dir, basename:, verbose:) quiet_flags = verbose ? [] : ["-qq"] result = system_command! "unzip",