Merge pull request #9199 from reitermarkus/type-signatures

Add more type signatures and `rspec-sorbet`.
This commit is contained in:
Markus Reiter 2020-11-19 18:11:51 +01:00 committed by GitHub
commit ca333a5da0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 201 additions and 33 deletions

1
.gitignore vendored
View File

@ -128,6 +128,7 @@
**/vendor/bundle/ruby/*/gems/rspec-mocks-*/
**/vendor/bundle/ruby/*/gems/rspec-retry-*/
**/vendor/bundle/ruby/*/gems/rspec-support-*/
**/vendor/bundle/ruby/*/gems/rspec-sorbet-*/
**/vendor/bundle/ruby/*/gems/rspec-wait-*/
**/vendor/bundle/ruby/*/gems/rubocop-1*/
**/vendor/bundle/ruby/*/gems/rubocop-ast-*/

View File

@ -18,6 +18,7 @@ AllCops:
- 'Homebrew/sorbet/rbi/gems/**/*.rbi'
- 'Homebrew/sorbet/rbi/hidden-definitions/**/*.rbi'
- 'Homebrew/sorbet/rbi/todo.rbi'
- 'Homebrew/sorbet/rbi/upstream.rbi'
- 'Homebrew/bin/*'
- 'Homebrew/vendor/**/*'

View File

@ -10,6 +10,7 @@ gem "ronn", require: false
gem "rspec", require: false
gem "rspec-its", require: false
gem "rspec-retry", require: false
gem "rspec-sorbet", require: false
gem "rspec-wait", require: false
gem "rubocop", require: false
gem "simplecov", require: false

View File

@ -95,6 +95,9 @@ GEM
rspec-support (~> 3.10.0)
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-sorbet (1.7.0)
sorbet
sorbet-runtime
rspec-support (3.10.0)
rspec-wait (0.0.9)
rspec (>= 3, < 4)
@ -167,6 +170,7 @@ DEPENDENCIES
rspec
rspec-its
rspec-retry
rspec-sorbet
rspec-wait
rubocop
rubocop-performance

View File

@ -1,34 +1,44 @@
# typed: false
# typed: true
# frozen_string_literal: true
# Settings for the build environment.
#
# @api private
class BuildEnvironment
extend T::Sig
sig { params(settings: Symbol).void }
def initialize(*settings)
@settings = Set.new(*settings)
@settings = Set.new(settings)
end
sig { params(args: T::Enumerable[Symbol]).returns(T.self_type) }
def merge(*args)
@settings.merge(*args)
self
end
sig { params(o: Symbol).returns(T.self_type) }
def <<(o)
@settings << o
self
end
sig { returns(T::Boolean) }
def std?
@settings.include? :std
end
sig { returns(T::Boolean) }
def userpaths?
@settings.include? :userpaths
end
# DSL for specifying build environment settings.
module DSL
extend T::Sig
sig { params(settings: Symbol).returns(BuildEnvironment) }
def env(*settings)
@env ||= BuildEnvironment.new
@env.merge(settings)
@ -50,16 +60,18 @@ class BuildEnvironment
].freeze
private_constant :KEYS
sig { params(env: T.untyped).returns(T::Array[String]) }
def self.keys(env)
KEYS & env.keys
end
sig { params(env: T.untyped, f: IO).void }
def self.dump(env, f = $stdout)
keys = self.keys(env)
keys -= %w[CC CXX OBJC OBJCXX] if env["CC"] == env["HOMEBREW_CC"]
keys.each do |key|
value = env[key]
value = env.fetch(key)
s = +"#{key}: #{value}"
case key
when "CC", "CXX", "LD"

View File

@ -6,9 +6,10 @@ module Cask
#
# @api private
module Cache
module_function
extend T::Sig
def path
sig { returns(Pathname) }
def self.path
@path ||= HOMEBREW_CACHE/"Cask"
end
end

View File

@ -1,4 +1,4 @@
# typed: false
# typed: true
# frozen_string_literal: true
require "utils/user"
@ -10,13 +10,13 @@ module Cask
module Caskroom
extend T::Sig
module_function
def path
sig { returns(Pathname) }
def self.path
@path ||= HOMEBREW_PREFIX.join("Caskroom")
end
def ensure_caskroom_exists
sig { void }
def self.ensure_caskroom_exists
return if path.exist?
sudo = !path.parent.writable?
@ -32,8 +32,8 @@ module Cask
SystemCommand.run("/usr/bin/chgrp", args: ["admin", path], sudo: sudo)
end
sig { params(config: Config).returns(T::Array[Cask]) }
def casks(config: nil)
sig { params(config: T.nilable(Config)).returns(T::Array[Cask]) }
def self.casks(config: nil)
return [] unless path.exist?
Pathname.glob(path.join("*")).sort.select(&:directory?).map do |path|

View File

@ -8,19 +8,25 @@ module Cask
#
# @api private
class Pkg
extend T::Sig
sig { params(regexp: String, command: T.class_of(SystemCommand)).returns(T::Array[Pkg]) }
def self.all_matching(regexp, command)
command.run("/usr/sbin/pkgutil", args: ["--pkgs=#{regexp}"]).stdout.split("\n").map do |package_id|
new(package_id.chomp, command)
end
end
sig { returns(String) }
attr_reader :package_id
sig { params(package_id: String, command: T.class_of(SystemCommand)).void }
def initialize(package_id, command = SystemCommand)
@package_id = package_id
@command = command
end
sig { void }
def uninstall
unless pkgutil_bom_files.empty?
odebug "Deleting pkg files"
@ -65,23 +71,28 @@ module Cask
forget
end
sig { void }
def forget
odebug "Unregistering pkg receipt (aka forgetting)"
@command.run!("/usr/sbin/pkgutil", args: ["--forget", package_id], sudo: true)
end
sig { returns(T::Array[Pathname]) }
def pkgutil_bom_files
@pkgutil_bom_files ||= pkgutil_bom_all.select(&:file?) - pkgutil_bom_specials
end
sig { returns(T::Array[Pathname]) }
def pkgutil_bom_specials
@pkgutil_bom_specials ||= pkgutil_bom_all.select(&method(:special?))
end
sig { returns(T::Array[Pathname]) }
def pkgutil_bom_dirs
@pkgutil_bom_dirs ||= pkgutil_bom_all.select(&:directory?) - pkgutil_bom_specials
end
sig { returns(T::Array[Pathname]) }
def pkgutil_bom_all
@pkgutil_bom_all ||= @command.run!("/usr/sbin/pkgutil", args: ["--files", package_id])
.stdout
@ -90,6 +101,7 @@ module Cask
.reject(&MacOS.public_method(:undeletable?))
end
sig { returns(Pathname) }
def root
@root ||= Pathname.new(info.fetch("volume")).join(info.fetch("install-location"))
end
@ -101,10 +113,12 @@ module Cask
private
sig { params(path: Pathname).returns(T::Boolean) }
def special?(path)
path.symlink? || path.chardev? || path.blockdev?
end
sig { params(path: Pathname).void }
def rmdir(path)
return unless path.children.empty?
@ -115,7 +129,8 @@ module Cask
end
end
def with_full_permissions(path)
sig { params(path: Pathname, _block: T.proc.void).void }
def with_full_permissions(path, &_block)
original_mode = (path.stat.mode % 01000).to_s(8)
original_flags = @command.run!("/usr/bin/stat", args: ["-f", "%Of", "--", path]).stdout.chomp
@ -128,10 +143,12 @@ module Cask
end
end
sig { params(paths: T::Array[Pathname]).returns(T::Array[Pathname]) }
def deepest_path_first(paths)
paths.sort_by { |path| -path.to_s.split(File::SEPARATOR).count }
end
sig { params(dir: Pathname).void }
def clean_ds_store(dir)
return unless (ds_store = dir.join(".DS_Store")).exist?
@ -140,12 +157,14 @@ module Cask
# Some packages leave broken symlinks around; we clean them out before
# attempting to `rmdir` to prevent extra cruft from accumulating.
sig { params(dir: Pathname).void }
def clean_broken_symlinks(dir)
dir.children.select(&method(:broken_symlink?)).each do |path|
@command.run!("/bin/rm", args: ["--", path], sudo: true)
end
end
sig { params(path: Pathname).returns(T::Boolean) }
def broken_symlink?(path)
path.symlink? && !path.exist?
end

View File

@ -1,4 +1,4 @@
# typed: false
# typed: true
# frozen_string_literal: true
require "utils/user"
@ -8,25 +8,35 @@ module Cask
#
# @api private
module Staged
extend T::Sig
# FIXME: Enable cop again when https://github.com/sorbet/sorbet/issues/3532 is fixed.
# rubocop:disable Style/MutableConstant
Paths = T.type_alias { T.any(String, Pathname, T::Array[T.any(String, Pathname)]) }
# rubocop:enable Style/MutableConstant
sig { params(paths: Paths, permissions_str: String).void }
def set_permissions(paths, permissions_str)
full_paths = remove_nonexistent(paths)
return if full_paths.empty?
@command.run!("/bin/chmod", args: ["-R", "--", permissions_str] + full_paths,
@command.run!("/bin/chmod", args: ["-R", "--", permissions_str, *full_paths],
sudo: false)
end
def set_ownership(paths, user: User.current, group: "staff")
sig { params(paths: Paths, user: T.any(String, User), group: String).void }
def set_ownership(paths, user: T.must(User.current), group: "staff")
full_paths = remove_nonexistent(paths)
return if full_paths.empty?
ohai "Changing ownership of paths required by #{@cask}; your password may be necessary"
@command.run!("/usr/sbin/chown", args: ["-R", "--", "#{user}:#{group}"] + full_paths,
@command.run!("/usr/sbin/chown", args: ["-R", "--", "#{user}:#{group}", *full_paths],
sudo: true)
end
private
sig { params(paths: Paths).returns(T::Array[Pathname]) }
def remove_nonexistent(paths)
Array(paths).map { |p| Pathname(p).expand_path }.select(&:exist?)
end

View File

@ -0,0 +1,7 @@
# typed: strict
module Cask
module Staged
include Kernel
end
end

View File

@ -1,4 +1,4 @@
# typed: false
# typed: true
# frozen_string_literal: true
require "tsort"
@ -8,7 +8,11 @@ module Cask
class TopologicalHash < Hash
include TSort
alias tsort_each_node each_key
private
def tsort_each_node(&block)
each_key(&block)
end
def tsort_each_child(node, &block)
fetch(node).each(&block)

View File

@ -47,11 +47,10 @@ module Homebrew
Utils::Shell.from_path(args.shell)
end
env_keys = BuildEnvironment.keys(ENV)
if shell.nil?
BuildEnvironment.dump ENV
else
env_keys.each do |key|
BuildEnvironment.keys(ENV).each do |key|
puts Utils::Shell.export_value(key, ENV[key], shell)
end
end

View File

@ -0,0 +1,8 @@
# DO NOT EDIT MANUALLY
# This is an autogenerated file for types exported from the `rspec-sorbet` gem.
# Please instead update this file by running `tapioca sync`.
# typed: true
# THIS IS AN EMPTY RBI FILE.
# see https://github.com/Shopify/tapioca/blob/master/README.md#manual-gem-requires

View File

@ -3030,6 +3030,16 @@ class BottleSpecification
extend ::T::Private::Methods::SingletonMethodHooks
end
module BuildEnvironment::DSL
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class BuildEnvironment
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
Bundler::Deprecate = Gem::Deprecate
class Bundler::Env
@ -5479,6 +5489,11 @@ class Cask::Audit
extend ::T::Private::Methods::SingletonMethodHooks
end
module Cask::Cache
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class Cask::Cask
def app(&block); end
@ -5806,11 +5821,21 @@ class Cask::MultipleCaskErrors
extend ::T::Private::Methods::SingletonMethodHooks
end
class Cask::Pkg
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module Cask::Quarantine
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module Cask::Staged
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module Cask::Utils
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
@ -8046,6 +8071,11 @@ class GitHub::Actions::Annotation
extend ::T::Private::Methods::SingletonMethodHooks
end
module GitHub::Actions
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module GitHub
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
@ -8132,6 +8162,11 @@ class Homebrew::CLI::Args
extend ::T::Private::Methods::SingletonMethodHooks
end
class Homebrew::CLI::NamedArgs
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
class Homebrew::CLI::Parser
include ::Homebrew::CLI::Parser::Compat
end
@ -8287,6 +8322,11 @@ class Homebrew::Style::LineLocation
extend ::T::Private::Methods::SingletonMethodHooks
end
class Homebrew::TapAuditor
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module Homebrew
extend ::FileUtils::StreamUtils_
extend ::T::Private::Methods::MethodHooks
@ -13289,8 +13329,6 @@ end
class Net::HTTPAlreadyReported
end
Net::HTTPClientError::EXCEPTION_TYPE = Net::HTTPServerException
Net::HTTPClientErrorCode = Net::HTTPClientError
class Net::HTTPEarlyHints
@ -13352,8 +13390,6 @@ end
class Net::HTTPRangeNotSatisfiable
end
Net::HTTPRedirection::EXCEPTION_TYPE = Net::HTTPRetriableError
Net::HTTPRedirectionCode = Net::HTTPRedirection
Net::HTTPRequestURITooLarge = Net::HTTPURITooLong
@ -13362,8 +13398,6 @@ Net::HTTPResponceReceiver = Net::HTTPResponse
Net::HTTPRetriableCode = Net::HTTPRedirection
Net::HTTPServerError::EXCEPTION_TYPE = Net::HTTPFatalError
Net::HTTPServerErrorCode = Net::HTTPServerError
Net::HTTPSession = Net::HTTP
@ -25362,6 +25396,25 @@ end
RSpec::SharedContext = RSpec::Core::SharedContext
module RSpec::Sorbet
end
module RSpec::Sorbet::Doubles
def allow_doubles!(); end
def allow_instance_doubles!(); end
INLINE_DOUBLE_REGEX = ::T.let(nil, ::T.untyped)
TYPED_ARRAY_MESSAGE = ::T.let(nil, ::T.untyped)
VERIFYING_DOUBLE_OR_DOUBLE = ::T.let(nil, ::T.untyped)
end
module RSpec::Sorbet::Doubles
end
module RSpec::Sorbet
extend ::RSpec::Sorbet::Doubles
end
module RSpec::Support
DEFAULT_FAILURE_NOTIFIER = ::T.let(nil, ::T.untyped)
DEFAULT_WARNING_NOTIFIER = ::T.let(nil, ::T.untyped)
@ -30740,6 +30793,16 @@ module Utils::Inreplace
extend ::T::Private::Methods::SingletonMethodHooks
end
class Utils::Shebang::RewriteInfo
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module Utils::Shebang
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks
end
module Utils::Shell
extend ::T::Private::Methods::MethodHooks
extend ::T::Private::Methods::SingletonMethodHooks

View File

@ -0,0 +1,11 @@
# typed: strict
class Pathname
# https://github.com/sorbet/sorbet/pull/3676
sig { params(p1: T.any(String, Pathname), p2: String).returns(T::Array[Pathname]) }
def self.glob(p1, p2 = T.unsafe(nil)); end
# https://github.com/sorbet/sorbet/pull/3678
sig { params(with_directory: T::Boolean).returns(T::Array[Pathname]) }
def children(with_directory = true); end
end

View File

@ -26,6 +26,7 @@ end
require "rspec/its"
require "rspec/wait"
require "rspec/retry"
require "rspec/sorbet"
require "rubocop"
require "rubocop/rspec/support"
require "find"
@ -58,6 +59,10 @@ TEST_DIRECTORIES = [
HOMEBREW_TEMP,
].freeze
# Make `instance_double` and `class_double`
# work when type-checking is active.
RSpec::Sorbet.allow_doubles!
RSpec.configure do |config|
config.order = :random

View File

@ -1,4 +1,4 @@
# typed: false
# typed: true
# frozen_string_literal: true
require "utils/tty"
@ -8,6 +8,9 @@ module GitHub
#
# @api private
module Actions
extend T::Sig
sig { params(string: String).returns(String) }
def self.escape(string)
# See https://github.community/t/set-output-truncates-multiline-strings/16852/3.
string.gsub("%", "%25")
@ -19,6 +22,7 @@ module GitHub
class Annotation
extend T::Sig
sig { params(path: T.any(String, Pathname)).returns(T.nilable(Pathname)) }
def self.path_relative_to_workspace(path)
workspace = Pathname(ENV.fetch("GITHUB_WORKSPACE", Dir.pwd)).realpath
path = Pathname(path)
@ -27,6 +31,12 @@ module GitHub
path.realpath.relative_path_from(workspace)
end
sig do
params(
type: Symbol, message: String,
file: T.nilable(T.any(String, Pathname)), line: T.nilable(Integer), column: T.nilable(Integer)
).void
end
def initialize(type, message, file: nil, line: nil, column: nil)
raise ArgumentError, "Unsupported type: #{type.inspect}" unless [:warning, :error].include?(type)
@ -39,17 +49,23 @@ module GitHub
sig { returns(String) }
def to_s
file = "file=#{Actions.escape(@file.to_s)}" if @file
line = "line=#{@line}" if @line
column = "col=#{@column}" if @column
metadata = @type.to_s
metadata = [*file, *line, *column].join(",").presence&.prepend(" ")
if @file
metadata << " file=#{Actions.escape(@file.to_s)}"
"::#{@type}#{metadata}::#{Actions.escape(@message)}"
if @line
metadata << ",line=#{@line}"
metadata << ",col=#{@column}" if @column
end
end
"::#{metadata}::#{Actions.escape(@message)}"
end
# An annotation is only relevant if the corresponding `file` is relative to
# the `GITHUB_WORKSPACE` directory or if no `file` is specified.
sig { returns(T::Boolean) }
def relevant?
return true if @file.nil?

View File

@ -6,14 +6,19 @@ module Utils
#
# @api private
module Shebang
extend T::Sig
module_function
# Specification on how to rewrite a given shebang.
#
# @api private
class RewriteInfo
extend T::Sig
attr_reader :regex, :max_length, :replacement
sig { params(regex: Regexp, max_length: Integer, replacement: T.any(String, Pathname)).void }
def initialize(regex, max_length, replacement)
@regex = regex
@max_length = max_length
@ -27,6 +32,7 @@ module Utils
# rewrite_shebang detected_python_shebang, bin/"script.py"
#
# @api public
sig { params(rewrite_info: RewriteInfo, paths: T::Array[T.any(String, Pathname)]).void }
def rewrite_shebang(rewrite_info, *paths)
paths.each do |f|
f = Pathname(f)