Enable recursive typechecking in specs

This commit is contained in:
Douglas Eichelberger 2025-09-05 19:31:34 -07:00
parent 5f1241b953
commit 3646b17de7
No known key found for this signature in database
GPG Key ID: F90193CBD547EB81
45 changed files with 223 additions and 141 deletions

View File

@ -9,7 +9,7 @@ class BottleSpecification
attr_reader :collector attr_reader :collector
sig { returns(T::Hash[String, T.untyped]) } sig { returns(T::Hash[Symbol, T.untyped]) }
attr_reader :root_url_specs attr_reader :root_url_specs
sig { returns(String) } sig { returns(String) }
@ -20,7 +20,7 @@ class BottleSpecification
@rebuild = T.let(0, Integer) @rebuild = T.let(0, Integer)
@repository = T.let(Homebrew::DEFAULT_REPOSITORY, String) @repository = T.let(Homebrew::DEFAULT_REPOSITORY, String)
@collector = T.let(Utils::Bottles::Collector.new, Utils::Bottles::Collector) @collector = T.let(Utils::Bottles::Collector.new, Utils::Bottles::Collector)
@root_url_specs = T.let({}, T::Hash[String, T.untyped]) @root_url_specs = T.let({}, T::Hash[Symbol, T.untyped])
@root_url = T.let(nil, T.nilable(String)) @root_url = T.let(nil, T.nilable(String))
end end

View File

@ -135,9 +135,9 @@ class Build
end end
new_env = { new_env = {
"TMPDIR" => HOMEBREW_TEMP, "TMPDIR" => HOMEBREW_TEMP.to_s,
"TEMP" => HOMEBREW_TEMP, "TEMP" => HOMEBREW_TEMP.to_s,
"TMP" => HOMEBREW_TEMP, "TMP" => HOMEBREW_TEMP.to_s,
} }
with_env(new_env) do with_env(new_env) do

View File

@ -37,11 +37,11 @@ module Homebrew
private private
sig { returns(T::Hash[Symbol, T::Array[String]]) } sig { returns(T::Hash[Symbol, T.nilable(T::Array[String])]) }
def skipped_entries def skipped_entries
return @skipped_entries if @skipped_entries return @skipped_entries if @skipped_entries
@skipped_entries ||= T.let({}, T.nilable(T::Hash[Symbol, T::Array[String]])) @skipped_entries ||= T.let({}, T.nilable(T::Hash[Symbol, T.nilable(T::Array[String])]))
[:brew, :cask, :mas, :tap, :whalebrew].each do |type| [:brew, :cask, :mas, :tap, :whalebrew].each do |type|
@skipped_entries[type] = @skipped_entries[type] =
ENV["HOMEBREW_BUNDLE_#{type.to_s.upcase}_SKIP"]&.split ENV["HOMEBREW_BUNDLE_#{type.to_s.upcase}_SKIP"]&.split

View File

@ -12,6 +12,7 @@ module Cask
# #
# @api internal # @api internal
class Config class Config
ConfigHash = T.type_alias { T::Hash[Symbol, T.any(LazyObject, String, Pathname, T::Array[String])] }
DEFAULT_DIRS = T.let( DEFAULT_DIRS = T.let(
{ {
appdir: "/Applications", appdir: "/Applications",
@ -33,7 +34,8 @@ module Cask
T::Hash[Symbol, String], T::Hash[Symbol, String],
) )
sig { returns(T::Hash[Symbol, String]) } # runtime recursive evaluation forces the LazyObject to be evaluated
T::Sig::WithoutRuntime.sig { returns(T::Hash[Symbol, T.any(LazyObject, String)]) }
def self.defaults def self.defaults
{ {
languages: LazyObject.new { ::OS::Mac.languages }, languages: LazyObject.new { ::OS::Mac.languages },
@ -67,35 +69,25 @@ module Cask
sig { params(json: String, ignore_invalid_keys: T::Boolean).returns(T.attached_class) } sig { params(json: String, ignore_invalid_keys: T::Boolean).returns(T.attached_class) }
def self.from_json(json, ignore_invalid_keys: false) def self.from_json(json, ignore_invalid_keys: false)
config = JSON.parse(json) config = JSON.parse(json, symbolize_names: true)
new( new(
default: config.fetch("default", {}), default: config.fetch(:default, {}),
env: config.fetch("env", {}), env: config.fetch(:env, {}),
explicit: config.fetch("explicit", {}), explicit: config.fetch(:explicit, {}),
ignore_invalid_keys:, ignore_invalid_keys:,
) )
end end
sig { sig { params(config: ConfigHash).returns(ConfigHash) }
params(
config: T::Enumerable[
[T.any(String, Symbol), T.any(String, Pathname, T::Array[String])],
],
).returns(
T::Hash[Symbol, T.any(String, Pathname, T::Array[String])],
)
}
def self.canonicalize(config) def self.canonicalize(config)
config.to_h do |k, v| config.to_h do |k, v|
key = k.to_sym if DEFAULT_DIRS.key?(k)
if DEFAULT_DIRS.key?(key)
raise TypeError, "Invalid path for default dir #{k}: #{v.inspect}" if v.is_a?(Array) raise TypeError, "Invalid path for default dir #{k}: #{v.inspect}" if v.is_a?(Array)
[key, Pathname(v).expand_path] [k, Pathname(v.to_s).expand_path]
else else
[key, v] [k, v]
end end
end end
end end
@ -103,14 +95,14 @@ module Cask
# Get the explicit configuration. # Get the explicit configuration.
# #
# @api internal # @api internal
sig { returns(T::Hash[Symbol, T.any(String, Pathname, T::Array[String])]) } sig { returns(ConfigHash) }
attr_accessor :explicit attr_accessor :explicit
sig { sig {
params( params(
default: T.nilable(T::Hash[Symbol, T.any(String, Pathname, T::Array[String])]), default: T.nilable(ConfigHash),
env: T.nilable(T::Hash[Symbol, T.any(String, Pathname, T::Array[String])]), env: T.nilable(ConfigHash),
explicit: T::Hash[Symbol, T.any(String, Pathname, T::Array[String])], explicit: ConfigHash,
ignore_invalid_keys: T::Boolean, ignore_invalid_keys: T::Boolean,
).void ).void
} }
@ -118,18 +110,18 @@ module Cask
if default if default
@default = T.let( @default = T.let(
self.class.canonicalize(self.class.defaults.merge(default)), self.class.canonicalize(self.class.defaults.merge(default)),
T.nilable(T::Hash[Symbol, T.any(String, Pathname, T::Array[String])]), T.nilable(ConfigHash),
) )
end end
if env if env
@env = T.let( @env = T.let(
self.class.canonicalize(env), self.class.canonicalize(env),
T.nilable(T::Hash[Symbol, T.any(String, Pathname, T::Array[String])]), T.nilable(ConfigHash),
) )
end end
@explicit = T.let( @explicit = T.let(
self.class.canonicalize(explicit), self.class.canonicalize(explicit),
T::Hash[Symbol, T.any(String, Pathname, T::Array[String])], ConfigHash,
) )
if ignore_invalid_keys if ignore_invalid_keys
@ -142,18 +134,18 @@ module Cask
@explicit.assert_valid_keys(*self.class.defaults.keys) @explicit.assert_valid_keys(*self.class.defaults.keys)
end end
sig { returns(T::Hash[Symbol, T.any(String, Pathname, T::Array[String])]) } sig { returns(ConfigHash) }
def default def default
@default ||= self.class.canonicalize(self.class.defaults) @default ||= self.class.canonicalize(self.class.defaults)
end end
sig { returns(T::Hash[Symbol, T.any(String, Pathname, T::Array[String])]) } sig { returns(ConfigHash) }
def env def env
@env ||= self.class.canonicalize( @env ||= self.class.canonicalize(
Homebrew::EnvConfig.cask_opts Homebrew::EnvConfig.cask_opts
.select { |arg| arg.include?("=") } .select { |arg| arg.include?("=") }
.map { |arg| T.cast(arg.split("=", 2), [String, String]) } .map { |arg| T.cast(arg.split("=", 2), [String, String]) }
.map do |(flag, value)| .to_h do |(flag, value)|
key = flag.sub(/^--/, "") key = flag.sub(/^--/, "")
# converts --language flag to :languages config key # converts --language flag to :languages config key
if key == "language" if key == "language"
@ -161,7 +153,7 @@ module Cask
value = value.split(",") value = value.split(",")
end end
[key, value] [key.to_sym, value]
end, end,
) )
end end

View File

@ -11,7 +11,7 @@ module Cask
sig { returns(T.nilable(T::Array[T.untyped])) } sig { returns(T.nilable(T::Array[T.untyped])) }
attr_accessor :uninstall_artifacts attr_accessor :uninstall_artifacts
sig { params(attributes: T::Hash[String, T.untyped]).void } sig { params(attributes: T.any(T::Hash[String, T.untyped], T::Hash[Symbol, T.untyped])).void }
def initialize(attributes = {}) def initialize(attributes = {})
@uninstall_flight_blocks = T.let(nil, T.nilable(T::Boolean)) @uninstall_flight_blocks = T.let(nil, T.nilable(T::Boolean))
@uninstall_artifacts = T.let(nil, T.nilable(T::Array[T.untyped])) @uninstall_artifacts = T.let(nil, T.nilable(T::Array[T.untyped]))

View File

@ -54,7 +54,7 @@ module Cask
revisions: T.nilable(T::Hash[T.any(Symbol, String), String]), revisions: T.nilable(T::Hash[T.any(Symbol, String), String]),
revision: T.nilable(String), revision: T.nilable(String),
trust_cert: T.nilable(T::Boolean), trust_cert: T.nilable(T::Boolean),
cookies: T.nilable(T::Hash[String, String]), cookies: T.nilable(T::Hash[T.any(String, Symbol), String]),
referer: T.nilable(T.any(URI::Generic, String)), referer: T.nilable(T.any(URI::Generic, String)),
header: T.nilable(T.any(String, T::Array[String])), header: T.nilable(T.any(String, T::Array[String])),
user_agent: T.nilable(T.any(Symbol, String)), user_agent: T.nilable(T.any(Symbol, String)),
@ -80,7 +80,8 @@ module Cask
specs[:revisions] = @revisions = T.let(revisions, T.nilable(T::Hash[T.any(Symbol, String), String])) specs[:revisions] = @revisions = T.let(revisions, T.nilable(T::Hash[T.any(Symbol, String), String]))
specs[:revision] = @revision = T.let(revision, T.nilable(String)) specs[:revision] = @revision = T.let(revision, T.nilable(String))
specs[:trust_cert] = @trust_cert = T.let(trust_cert, T.nilable(T::Boolean)) specs[:trust_cert] = @trust_cert = T.let(trust_cert, T.nilable(T::Boolean))
specs[:cookies] = @cookies = T.let(cookies, T.nilable(T::Hash[String, String])) specs[:cookies] =
@cookies = T.let(cookies&.transform_keys(&:to_s), T.nilable(T::Hash[String, String]))
specs[:referer] = @referer = T.let(referer, T.nilable(T.any(URI::Generic, String))) specs[:referer] = @referer = T.let(referer, T.nilable(T.any(URI::Generic, String)))
specs[:headers] = @header = T.let(header, T.nilable(T.any(String, T::Array[String]))) specs[:headers] = @header = T.let(header, T.nilable(T.any(String, T::Array[String])))
specs[:user_agent] = @user_agent = T.let(user_agent || :default, T.nilable(T.any(Symbol, String))) specs[:user_agent] = @user_agent = T.let(user_agent || :default, T.nilable(T.any(Symbol, String)))

View File

@ -45,7 +45,7 @@ class CaskDependent
) )
end end
sig { returns(T::Array[CaskDependent::Requirement]) } sig { returns(T::Array[::Requirement]) }
def requirements def requirements
@requirements ||= T.let( @requirements ||= T.let(
begin begin
@ -73,7 +73,7 @@ class CaskDependent
requirements requirements
end, end,
T.nilable(T::Array[CaskDependent::Requirement]), T.nilable(T::Array[::Requirement]),
) )
end end

View File

@ -9,7 +9,7 @@ module Homebrew
# 1: long option name (e.g. "--debug") # 1: long option name (e.g. "--debug")
# 2: option description (e.g. "Print debugging information") # 2: option description (e.g. "Print debugging information")
# 3: whether the option is hidden # 3: whether the option is hidden
OptionsType = T.type_alias { T::Array[[String, T.nilable(String), String, T::Boolean]] } OptionsType = T.type_alias { T::Array[[T.nilable(String), T.nilable(String), String, T::Boolean]] }
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
attr_reader :options_only, :flags_only, :remaining attr_reader :options_only, :flags_only, :remaining
@ -170,7 +170,7 @@ module Homebrew
sig { returns(T::Array[String]) } sig { returns(T::Array[String]) }
def cli_args def cli_args
@cli_args ||= @processed_options.filter_map do |short, long| @cli_args ||= @processed_options.filter_map do |short, long|
option = long || short option = T.must(long || short)
switch = :"#{option_to_name(option)}?" switch = :"#{option_to_name(option)}?"
flag = option_to_name(option).to_sym flag = option_to_name(option).to_sym
if @table[switch] == true || @table[flag] == true if @table[switch] == true || @table[flag] == true

View File

@ -127,9 +127,7 @@ module Homebrew
sig { sig {
params( params(
formulae_or_casks: T::Array[T.any(Formula, Cask::Cask)], formulae_or_casks: T::Array[T.any(Formula, Cask::Cask)],
).returns( ).returns(T::Array[T::Hash[Symbol, T.untyped]])
T::Array[T.any(T::Hash[String, T.untyped], T::Hash[String, T.untyped])],
)
} }
def json_info(formulae_or_casks) def json_info(formulae_or_casks)
formulae_or_casks.map do |formula_or_cask| formulae_or_casks.map do |formula_or_cask|

View File

@ -427,6 +427,19 @@ require "extend/os/cmd/update-report"
class Reporter class Reporter
include Utils::Output::Mixin include Utils::Output::Mixin
Report = T.type_alias do
{
A: T::Array[String],
AC: T::Array[String],
D: T::Array[String],
DC: T::Array[String],
M: T::Array[String],
MC: T::Array[String],
R: T::Array[[String, String]],
RC: T::Array[[String, String]],
}
end
class ReporterRevisionUnsetError < RuntimeError class ReporterRevisionUnsetError < RuntimeError
sig { params(var_name: String).void } sig { params(var_name: String).void }
def initialize(var_name) def initialize(var_name)
@ -456,14 +469,17 @@ class Reporter
raise ReporterRevisionUnsetError, current_revision_var if @current_revision.empty? raise ReporterRevisionUnsetError, current_revision_var if @current_revision.empty?
end end
@report = T.let(nil, T.nilable(T::Hash[Symbol, T::Array[String]])) @report = T.let(nil, T.nilable(Report))
end end
sig { params(auto_update: T::Boolean).returns(T::Hash[Symbol, T::Array[String]]) } sig { params(auto_update: T::Boolean).returns(Report) }
def report(auto_update: false) def report(auto_update: false)
return @report if @report return @report if @report
@report = Hash.new { |h, k| h[k] = [] } @report = {
A: [], AC: [], D: [], DC: [], M: [], MC: [], R: T.let([], T::Array[[String, String]]),
RC: T.let([], T::Array[[String, String]])
}
return @report unless updated? return @report unless updated?
diff.each_line do |line| diff.each_line do |line|
@ -791,13 +807,15 @@ class ReporterHub
sig { void } sig { void }
def initialize def initialize
@hash = T.let({}, T::Hash[Symbol, T::Array[String]]) @hash = T.let({}, T::Hash[Symbol, T::Array[T.any(String, [String, String])]])
@reporters = T.let([], T::Array[Reporter]) @reporters = T.let([], T::Array[Reporter])
end end
sig { params(key: Symbol).returns(T::Array[String]) } sig { params(key: Symbol).returns(T::Array[String]) }
def select_formula_or_cask(key) def select_formula_or_cask(key)
@hash.fetch(key, []) raise "Unsupported key #{key}" unless [:A, :AC, :D, :DC, :M, :MC].include?(key)
T.cast(@hash.fetch(key, []), T::Array[String])
end end
sig { params(reporter: Reporter, auto_update: T::Boolean).void } sig { params(reporter: Reporter, auto_update: T::Boolean).void }

View File

@ -204,7 +204,7 @@ module Commands
cmd_parser.processed_options.filter_map do |short, long, desc, hidden| cmd_parser.processed_options.filter_map do |short, long, desc, hidden|
next if hidden next if hidden
[long || short, desc] [T.must(long || short), desc]
end end
else else
options = [] options = []

View File

@ -135,7 +135,7 @@ class DependencyCollector
sig { sig {
params(spec: T.any(String, Resource, Symbol, Requirement, Dependency, Class), params(spec: T.any(String, Resource, Symbol, Requirement, Dependency, Class),
tags: T::Array[Symbol]).returns(T.any(Dependency, Requirement, Array, NilClass)) tags: T::Array[T.any(String, Symbol)]).returns(T.any(Dependency, Requirement, Array, NilClass))
} }
def parse_spec(spec, tags) def parse_spec(spec, tags)
raise ArgumentError, "Implicit dependencies cannot be manually specified" if tags.include?(:implicit) raise ArgumentError, "Implicit dependencies cannot be manually specified" if tags.include?(:implicit)

View File

@ -181,7 +181,7 @@ module Homebrew
end end
sig { sig {
params(old_keys: T::Array[String], old_bottle_spec: BottleSpecification, params(old_keys: T::Array[Symbol], old_bottle_spec: BottleSpecification,
new_bottle_hash: T::Hash[String, T.untyped]) new_bottle_hash: T::Hash[String, T.untyped])
.returns([T::Array[String], T::Array[T::Hash[Symbol, T.any(String, Symbol)]]]) .returns([T::Array[String], T::Array[T::Hash[Symbol, T.any(String, Symbol)]]])
} }
@ -506,7 +506,7 @@ module Homebrew
tab.time = nil tab.time = nil
tab.changed_files = changed_files.dup tab.changed_files = changed_files.dup
if args.only_json_tab? if args.only_json_tab?
tab.changed_files.delete(Pathname.new(AbstractTab::FILENAME)) tab.changed_files&.delete(Pathname.new(AbstractTab::FILENAME))
tab.tabfile.unlink tab.tabfile.unlink
else else
tab.write tab.write

View File

@ -119,7 +119,7 @@ module Homebrew
# seeds being output when running parallel tests. # seeds being output when running parallel tests.
seed = args.seed || rand(0xFFFF).to_i seed = args.seed || rand(0xFFFF).to_i
bundle_args = ["-I", HOMEBREW_LIBRARY_PATH/"test"] bundle_args = ["-I", (HOMEBREW_LIBRARY_PATH/"test").to_s]
bundle_args += %W[ bundle_args += %W[
--seed #{seed} --seed #{seed}
--color --color
@ -249,6 +249,7 @@ module Homebrew
ENV["HOMEBREW_TEST_GENERIC_OS"] = "1" if args.generic? ENV["HOMEBREW_TEST_GENERIC_OS"] = "1" if args.generic?
ENV["HOMEBREW_TEST_ONLINE"] = "1" if args.online? ENV["HOMEBREW_TEST_ONLINE"] = "1" if args.online?
ENV["HOMEBREW_SORBET_RUNTIME"] = "1" ENV["HOMEBREW_SORBET_RUNTIME"] = "1"
ENV["HOMEBREW_SORBET_RECURSIVE"] = "1"
ENV["USER"] ||= system_command!("id", args: ["-nu"]).stdout.chomp ENV["USER"] ||= system_command!("id", args: ["-nu"]).stdout.chomp

View File

@ -1198,9 +1198,9 @@ module Homebrew
private private
sig { returns(T::Array[Pathname]) } sig { returns(T::Array[String]) }
def paths def paths
@paths ||= T.let(ORIGINAL_PATHS.uniq.map(&:to_s), T.nilable(T::Array[Pathname])) @paths ||= T.let(ORIGINAL_PATHS.uniq.map(&:to_s), T.nilable(T::Array[String]))
end end
end end
end end

View File

@ -643,11 +643,9 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
args args
end end
sig { returns(T::Hash[Symbol, String]) } sig { returns(T::Hash[Symbol, T.any(String, Symbol)]) }
def _curl_opts def _curl_opts
return { user_agent: meta.fetch(:user_agent) } if meta.key?(:user_agent) meta.slice(:user_agent)
{}
end end
sig { override.params(args: String, options: T.untyped).returns(SystemCommand::Result) } sig { override.params(args: String, options: T.untyped).returns(SystemCommand::Result) }

View File

@ -471,6 +471,11 @@ module Homebrew
"of macOS. This is useful in development on new macOS versions.", "of macOS. This is useful in development on new macOS versions.",
boolean: true, boolean: true,
}, },
HOMEBREW_SORBET_RECURSIVE: {
description: "If set along with `$HOMEBREW_SORBET_RUNTIME`, enable recursive typechecking using Sorbet. " \
"Auomatically enabled when running tests.",
boolean: true,
},
HOMEBREW_SORBET_RUNTIME: { HOMEBREW_SORBET_RUNTIME: {
description: "If set, enable runtime typechecking using Sorbet. " \ description: "If set, enable runtime typechecking using Sorbet. " \
"Set by default for `$HOMEBREW_DEVELOPER` or when running some developer commands.", "Set by default for `$HOMEBREW_DEVELOPER` or when running some developer commands.",

View File

@ -244,7 +244,7 @@ module Superenv
end end
# Don't add `llvm` to library paths; this leads to undesired linkage to LLVM's `libunwind` # Don't add `llvm` to library paths; this leads to undesired linkage to LLVM's `libunwind`
paths << keg_only_deps.reject { |dep| dep.name.match?(/^llvm(@\d+)?$/) } paths += keg_only_deps.reject { |dep| dep.name.match?(/^llvm(@\d+)?$/) }
.map(&:opt_lib) .map(&:opt_lib)
paths << (HOMEBREW_PREFIX/"lib") paths << (HOMEBREW_PREFIX/"lib")

View File

@ -34,7 +34,7 @@ module Kernel
sig { type_parameters(:U).params(block: T.proc.returns(T.type_parameter(:U))).returns(T.type_parameter(:U)) } sig { type_parameters(:U).params(block: T.proc.returns(T.type_parameter(:U))).returns(T.type_parameter(:U)) }
def with_homebrew_path(&block) def with_homebrew_path(&block)
with_env(PATH: PATH.new(ORIGINAL_PATHS), &block) with_env(PATH: PATH.new(ORIGINAL_PATHS).to_s, &block)
end end
sig { sig {
@ -283,8 +283,10 @@ module Kernel
# @api public # @api public
sig { sig {
type_parameters(:U) type_parameters(:U)
.params(hash: T::Hash[Object, String], _block: T.proc.returns(T.type_parameter(:U))) .params(
.returns(T.type_parameter(:U)) hash: T::Hash[Object, T.any(NilClass, PATH, Pathname, String)],
_block: T.proc.returns(T.type_parameter(:U)),
).returns(T.type_parameter(:U))
} }
def with_env(hash, &_block) def with_env(hash, &_block)
old_values = {} old_values = {}
@ -292,7 +294,7 @@ module Kernel
hash.each do |key, value| hash.each do |key, value|
key = key.to_s key = key.to_s
old_values[key] = ENV.delete(key) old_values[key] = ENV.delete(key)
ENV[key] = value ENV[key] = value&.to_s
end end
yield yield

View File

@ -15,7 +15,7 @@ module OS
appdir: "~/.config/apps", appdir: "~/.config/apps",
}.freeze, T::Hash[Symbol, String]) }.freeze, T::Hash[Symbol, String])
sig { returns(T::Hash[Symbol, String]) } sig { returns(T::Hash[Symbol, T.any(LazyObject, String)]) }
def defaults def defaults
{ {
languages: LazyObject.new { Linux.languages }, languages: LazyObject.new { Linux.languages },

View File

@ -3963,7 +3963,7 @@ class Formula
# ``` # ```
# #
# @api public # @api public
sig { params(dep: T.any(String, Symbol, T::Hash[String, T.untyped], T::Class[Requirement])).void } sig { params(dep: T.any(String, Symbol, T::Hash[T.any(String, Symbol, T::Class[Requirement]), T.untyped], T::Class[Requirement])).void }
def depends_on(dep) def depends_on(dep)
specs.each { |spec| spec.depends_on(dep) } specs.each { |spec| spec.depends_on(dep) }
end end

View File

@ -809,7 +809,7 @@ on_request: installed_on_request?, options:)
else else
[] []
end end
options += effective_build_options_for(formula).used_options.to_a options += effective_build_options_for(formula).used_options.to_a.map(&:to_s)
options options
end end

View File

@ -51,7 +51,14 @@ module Formulary
end end
private_class_method :platform_cache_tag private_class_method :platform_cache_tag
sig { returns(T::Hash[Symbol, T::Hash[String, T.class_of(Formula)]]) } sig {
returns({
api: T.nilable(T::Hash[String, T.class_of(Formula)]),
formulary_factory: T.nilable(T::Hash[String, Formula]),
path: T.nilable(T::Hash[String, T.class_of(Formula)]),
stub: T.nilable(T::Hash[String, T.class_of(Formula)]),
})
}
def self.platform_cache def self.platform_cache
cache[platform_cache_tag] ||= {} cache[platform_cache_tag] ||= {}
end end

View File

@ -28,4 +28,7 @@ class LazyObject < Delegator
__getobj__.is_a?(klass) || super __getobj__.is_a?(klass) || super
end end
def class = __getobj__.class
def to_s = __getobj__.to_s
end end

View File

@ -14,7 +14,7 @@ class Messages
sig { returns(Integer) } sig { returns(Integer) }
attr_reader :package_count attr_reader :package_count
sig { returns(T::Array[T::Hash[String, Float]]) } sig { returns(T::Array[{ package: String, time: Float }]) }
attr_reader :install_times attr_reader :install_times
sig { void } sig { void }
@ -22,7 +22,7 @@ class Messages
@caveats = T.let([], T::Array[{ package: String, caveats: T.any(String, Caveats) }]) @caveats = T.let([], T::Array[{ package: String, caveats: T.any(String, Caveats) }])
@completions_and_elisp = T.let(Set.new, T::Set[String]) @completions_and_elisp = T.let(Set.new, T::Set[String])
@package_count = T.let(0, Integer) @package_count = T.let(0, Integer)
@install_times = T.let([], T::Array[T::Hash[String, Float]]) @install_times = T.let([], T::Array[{ package: String, time: Float }])
end end
sig { params(package: String, caveats: T.any(String, Caveats)).void } sig { params(package: String, caveats: T.any(String, Caveats)).void }

View File

@ -15,10 +15,10 @@ class XcodeRequirement < Requirement
xcode_installed_version! xcode_installed_version!
end end
sig { params(tags: T::Array[String]).void } sig { params(tags: T::Array[T.any(String, Symbol)]).void }
def initialize(tags = []) def initialize(tags = [])
version = tags.shift if tags.first.to_s.match?(/(\d\.)+\d/) version = tags.shift if tags.first.to_s.match?(/(\d\.)+\d/)
@version = T.let(version, T.nilable(String)) @version = T.let(version.to_s, T.nilable(String))
super super
end end

View File

@ -83,11 +83,11 @@ module RuboCop
) )
end end
sig { returns(T::Hash[Parser::Source::Range, T::Array[Parser::Source::Comment]]) } sig { returns(T::Hash[Parser::Source::Map, T::Array[Parser::Source::Comment]]) }
def comments_hash def comments_hash
@comments_hash ||= T.let( @comments_hash ||= T.let(
Parser::Source::Comment.associate_locations(stanza_node.parent, all_comments), Parser::Source::Comment.associate_locations(stanza_node.parent, all_comments),
T.nilable(T::Hash[Parser::Source::Range, T::Array[Parser::Source::Comment]]), T.nilable(T::Hash[Parser::Source::Map, T::Array[Parser::Source::Comment]]),
) )
end end

View File

@ -46,7 +46,7 @@ module RuboCop
sig { sig {
params( params(
block_node: RuboCop::AST::BlockNode, block_node: RuboCop::AST::BlockNode,
comments: T::Array[String], comments: T::Array[Parser::Source::Comment],
).returns( ).returns(
T::Array[RuboCop::Cask::AST::Stanza], T::Array[RuboCop::Cask::AST::Stanza],
) )

View File

@ -13,7 +13,7 @@ class SBOM
include Utils::Output::Mixin include Utils::Output::Mixin
FILENAME = "sbom.spdx.json" FILENAME = "sbom.spdx.json"
SCHEMA_FILE = (HOMEBREW_LIBRARY_PATH/"data/schemas/sbom.json").freeze SCHEMA_FILE = T.let((HOMEBREW_LIBRARY_PATH/"data/schemas/sbom.json").freeze, Pathname)
# Instantiates a {SBOM} for a new installation of a formula. # Instantiates a {SBOM} for a new installation of a formula.
sig { params(formula: Formula, tab: Tab).returns(T.attached_class) } sig { params(formula: Formula, tab: Tab).returns(T.attached_class) }
@ -62,7 +62,7 @@ class SBOM
formula.prefix/FILENAME formula.prefix/FILENAME
end end
sig { params(deps: T::Array[T::Hash[String, String]]).returns(T::Array[T::Hash[String, String]]) } sig { params(deps: T::Array[T::Hash[String, T.untyped]]).returns(T::Array[T::Hash[String, T.anything]]) }
def self.runtime_deps_hash(deps) def self.runtime_deps_hash(deps)
deps.map do |dep| deps.map do |dep|
full_name = dep.fetch("full_name") full_name = dep.fetch("full_name")
@ -83,12 +83,12 @@ class SBOM
spdxfile(formula).exist? spdxfile(formula).exist?
end end
sig { returns(T::Hash[String, T.untyped]) } sig { returns(T::Hash[String, T.anything]) }
def self.schema def self.schema
@schema ||= JSON.parse(SCHEMA_FILE.read, freeze: true) @schema ||= T.let(JSON.parse(SCHEMA_FILE.read, freeze: true), T.nilable(T::Hash[String, T.untyped]))
end end
sig { params(bottling: T::Boolean).returns(T::Array[T::Hash[String, T.untyped]]) } sig { params(bottling: T::Boolean).returns(T::Array[String]) }
def schema_validation_errors(bottling: false) def schema_validation_errors(bottling: false)
unless Homebrew.require? "json_schemer" unless Homebrew.require? "json_schemer"
error_message = "Need json_schemer to validate SBOM, run `brew install-bundler-gems --add-groups=bottle`!" error_message = "Need json_schemer to validate SBOM, run `brew install-bundler-gems --add-groups=bottle`!"
@ -134,17 +134,17 @@ class SBOM
attr_reader :name, :homebrew_version, :time, :stdlib, :source, :built_on, :license attr_reader :name, :homebrew_version, :time, :stdlib, :source, :built_on, :license
attr_accessor :spdxfile attr_accessor :spdxfile
sig { params(attributes: Hash).void } sig { params(attributes: T::Hash[Symbol, T.untyped]).void }
def initialize(attributes = {}) def initialize(attributes = {})
attributes.each { |key, value| instance_variable_set(:"@#{key}", value) } attributes.each { |key, value| instance_variable_set(:"@#{key}", value) }
end end
sig { sig {
params( params(
runtime_dependency_declaration: T::Array[Hash], runtime_dependency_declaration: T::Array[T::Hash[Symbol, T.untyped]],
compiler_declaration: Hash, compiler_declaration: T::Hash[String, T.untyped],
bottling: T::Boolean, bottling: T::Boolean,
).returns(T::Array[Hash]) ).returns(T::Array[T::Hash[Symbol, T.untyped]])
} }
def generate_relations_json(runtime_dependency_declaration, compiler_declaration, bottling:) def generate_relations_json(runtime_dependency_declaration, compiler_declaration, bottling:)
runtime = runtime_dependency_declaration.map do |dependency| runtime = runtime_dependency_declaration.map do |dependency|
@ -167,7 +167,7 @@ class SBOM
spdxElementId: "SPDXRef-File-#{name}", spdxElementId: "SPDXRef-File-#{name}",
relationshipType: "PACKAGE_OF", relationshipType: "PACKAGE_OF",
relatedSpdxElement: "SPDXRef-Archive-#{name}-src", relatedSpdxElement: "SPDXRef-Archive-#{name}-src",
}], T::Array[Hash]) }], T::Array[T::Hash[Symbol, T.untyped]])
unless bottling unless bottling
base << { base << {
@ -189,16 +189,11 @@ class SBOM
end end
sig { sig {
params(runtime_dependency_declaration: T::Array[Hash], params(
compiler_declaration: Hash, runtime_dependency_declaration: T::Array[T::Hash[Symbol, T.anything]],
bottling: T::Boolean).returns( compiler_declaration: T::Hash[String, T::Hash[Symbol, T.anything]],
T::Array[ bottling: T::Boolean,
T::Hash[ ).returns(T::Array[T::Hash[Symbol, T.untyped]])
Symbol,
T.any(String, T::Array[T::Hash[Symbol, String]]),
],
],
)
} }
def generate_packages_json(runtime_dependency_declaration, compiler_declaration, bottling:) def generate_packages_json(runtime_dependency_declaration, compiler_declaration, bottling:)
bottle = [] bottle = []
@ -259,9 +254,8 @@ class SBOM
end end
sig { sig {
params(bottling: T::Boolean).returns(T::Array[T::Hash[Symbol, params(bottling: T::Boolean)
T.any(T::Boolean, String, .returns(T::Array[T::Hash[Symbol, T.any(T::Boolean, String, T::Array[T::Hash[Symbol, String]])]])
T::Array[T::Hash[Symbol, String]])]])
} }
def full_spdx_runtime_dependencies(bottling:) def full_spdx_runtime_dependencies(bottling:)
return [] if bottling || @runtime_dependencies.blank? return [] if bottling || @runtime_dependencies.blank?
@ -302,7 +296,7 @@ class SBOM
end end
end end
sig { params(bottling: T::Boolean).returns(T::Hash[Symbol, T.any(String, T::Array[T::Hash[Symbol, String]])]) } sig { params(bottling: T::Boolean).returns(T::Hash[Symbol, T.anything]) }
def to_spdx_sbom(bottling:) def to_spdx_sbom(bottling:)
runtime_full = full_spdx_runtime_dependencies(bottling:) runtime_full = full_spdx_runtime_dependencies(bottling:)
@ -360,7 +354,7 @@ class SBOM
} }
end end
sig { params(base: T.nilable(T::Hash[String, Hash])).returns(T.nilable(T::Hash[String, String])) } sig { params(base: T.nilable(T::Hash[String, T.untyped])).returns(T.nilable(T::Hash[String, String])) }
def get_bottle_info(base) def get_bottle_info(base)
return unless base.present? return unless base.present?

View File

@ -337,7 +337,7 @@ module Homebrew
parsed parsed
end end
sig { params(variables: T::Hash[Symbol, String]).returns(T.nilable(T::Hash[Symbol, String])) } sig { params(variables: T::Hash[Symbol, T.any(Pathname, String)]).returns(T.nilable(T::Hash[Symbol, String])) }
def environment_variables(variables = {}) def environment_variables(variables = {})
@environment_variables = variables.transform_values(&:to_s) @environment_variables = variables.transform_values(&:to_s)
end end

View File

@ -219,7 +219,7 @@ class SoftwareSpec
options << opt options << opt
end end
sig { params(hash: T::Hash[T.any(String, Symbol), T.any(String, Symbol)]).void } sig { params(hash: T::Hash[T.any(String, Symbol, T::Array[String]), T.any(String, Symbol, T::Array[String])]).void }
def deprecated_option(hash) def deprecated_option(hash)
raise ArgumentError, "deprecated_option hash must not be empty" if hash.empty? raise ArgumentError, "deprecated_option hash must not be empty" if hash.empty?
@ -242,7 +242,7 @@ class SoftwareSpec
@build = BuildOptions.new(Options.create(@flags), options) @build = BuildOptions.new(Options.create(@flags), options)
end end
sig { params(spec: T.any(String, Symbol, T::Hash[String, T.untyped], T::Class[Requirement], Dependable)).void } sig { params(spec: T.any(String, Symbol, T::Hash[T.any(String, Symbol, T::Class[Requirement]), T.untyped], T::Class[Requirement], Dependable)).void }
def depends_on(spec) def depends_on(spec)
dep = dependency_collector.add(spec) dep = dependency_collector.add(spec)
add_dep_option(dep) if dep add_dep_option(dep) if dep

View File

@ -10,6 +10,55 @@ require "extend/module"
# There are mechanisms to achieve a middle ground (`default_checked_level`). # There are mechanisms to achieve a middle ground (`default_checked_level`).
if ENV["HOMEBREW_SORBET_RUNTIME"] if ENV["HOMEBREW_SORBET_RUNTIME"]
T::Configuration.enable_final_checks_on_hooks T::Configuration.enable_final_checks_on_hooks
if ENV["HOMEBREW_SORBET_RECURSIVE"] == "1"
module T
module Types
class FixedArray < Base
def valid?(obj) = recursively_valid?(obj)
end
class FixedHash < Base
def valid?(obj) = recursively_valid?(obj)
end
class Intersection < Base
def valid?(obj) = recursively_valid?(obj)
end
class TypedArray < TypedEnumerable
def valid?(obj) = recursively_valid?(obj)
end
class TypedEnumerable < Base
def valid?(obj) = recursively_valid?(obj)
end
class TypedEnumeratorChain < TypedEnumerable
def valid?(obj) = recursively_valid?(obj)
end
class TypedEnumeratorLazy < TypedEnumerable
def valid?(obj) = recursively_valid?(obj)
end
class TypedHash < TypedEnumerable
def valid?(obj) = recursively_valid?(obj)
end
class TypedRange < TypedEnumerable
def valid?(obj) = recursively_valid?(obj)
end
class TypedSet < TypedEnumerable
def valid?(obj) = recursively_valid?(obj)
end
class Union < Base
def valid?(obj) = recursively_valid?(obj)
end
end
end
end
else else
# Redefine `T.let`, etc. to make the `checked` parameter default to `false` rather than `true`. # Redefine `T.let`, etc. to make the `checked` parameter default to `false` rather than `true`.
# @private # @private

View File

@ -27,7 +27,7 @@ class SystemCommand
args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)], args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)],
sudo: T::Boolean, sudo: T::Boolean,
sudo_as_root: T::Boolean, sudo_as_root: T::Boolean,
env: T::Hash[String, String], env: T::Hash[String, T.any(NilClass, String, T::Boolean)],
input: T.any(String, T::Array[String]), input: T.any(String, T::Array[String]),
must_succeed: T::Boolean, must_succeed: T::Boolean,
print_stdout: T.any(T::Boolean, Symbol), print_stdout: T.any(T::Boolean, Symbol),
@ -56,7 +56,7 @@ class SystemCommand
args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)], args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)],
sudo: T::Boolean, sudo: T::Boolean,
sudo_as_root: T::Boolean, sudo_as_root: T::Boolean,
env: T::Hash[String, String], env: T::Hash[String, T.any(NilClass, String, T::Boolean)],
input: T.any(String, T::Array[String]), input: T.any(String, T::Array[String]),
print_stdout: T.any(T::Boolean, Symbol), print_stdout: T.any(T::Boolean, Symbol),
print_stderr: T.any(T::Boolean, Symbol), print_stderr: T.any(T::Boolean, Symbol),
@ -84,7 +84,7 @@ class SystemCommand
args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)], args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)],
sudo: T::Boolean, sudo: T::Boolean,
sudo_as_root: T::Boolean, sudo_as_root: T::Boolean,
env: T::Hash[String, String], env: T::Hash[String, T.any(NilClass, String, T::Boolean)],
input: T.any(String, T::Array[String]), input: T.any(String, T::Array[String]),
must_succeed: T::Boolean, must_succeed: T::Boolean,
print_stdout: T.any(T::Boolean, Symbol), print_stdout: T.any(T::Boolean, Symbol),
@ -110,7 +110,7 @@ class SystemCommand
args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)], args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)],
sudo: T::Boolean, sudo: T::Boolean,
sudo_as_root: T::Boolean, sudo_as_root: T::Boolean,
env: T::Hash[String, String], env: T::Hash[String, T.any(NilClass, String, T::Boolean)],
input: T.any(String, T::Array[String]), input: T.any(String, T::Array[String]),
must_succeed: T::Boolean, must_succeed: T::Boolean,
print_stdout: T.any(T::Boolean, Symbol), print_stdout: T.any(T::Boolean, Symbol),
@ -169,7 +169,7 @@ class SystemCommand
args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)], args: T::Array[T.any(String, Integer, Float, Pathname, URI::Generic)],
sudo: T::Boolean, sudo: T::Boolean,
sudo_as_root: T::Boolean, sudo_as_root: T::Boolean,
env: T::Hash[String, String], env: T::Hash[String, T.any(NilClass, String, T::Boolean)],
input: T.any(String, T::Array[String]), input: T.any(String, T::Array[String]),
must_succeed: T::Boolean, must_succeed: T::Boolean,
print_stdout: T.any(T::Boolean, Symbol), print_stdout: T.any(T::Boolean, Symbol),
@ -237,7 +237,7 @@ class SystemCommand
sig { returns(T.any(NilClass, String, Pathname)) } sig { returns(T.any(NilClass, String, Pathname)) }
attr_reader :chdir attr_reader :chdir
sig { returns(T::Hash[String, String]) } sig { returns(T::Hash[String, T.any(NilClass, String, T::Boolean)]) }
attr_reader :env attr_reader :env
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
@ -378,7 +378,7 @@ class SystemCommand
sig { sig {
params( params(
env: T::Hash[String, String], env: T::Hash[String, T.nilable(String)],
executable: String, executable: String,
args: String, args: String,
options: T.untyped, options: T.untyped,
@ -480,7 +480,7 @@ class SystemCommand
sig { sig {
params( params(
command: T::Array[String], command: T::Array[String],
output: T::Array[[Symbol, String]], output: T::Array[[T.any(String, Symbol), String]],
status: Process::Status, status: Process::Status,
secrets: T::Array[String], secrets: T::Array[String],
).void ).void

View File

@ -38,7 +38,8 @@ class AbstractTab
# @api internal # @api internal
attr_accessor :runtime_dependencies attr_accessor :runtime_dependencies
sig { params(attributes: T::Hash[String, T.untyped]).void } # TODO: Update attributes to only accept symbol keys (kwargs style).
sig { params(attributes: T.any(T::Hash[String, T.untyped], T::Hash[Symbol, T.untyped])).void }
def initialize(attributes = {}) def initialize(attributes = {})
@installed_as_dependency = T.let(nil, T.nilable(T::Boolean)) @installed_as_dependency = T.let(nil, T.nilable(T::Boolean))
@installed_on_request = T.let(nil, T.nilable(T::Boolean)) @installed_on_request = T.let(nil, T.nilable(T::Boolean))
@ -51,7 +52,14 @@ class AbstractTab
@built_on = T.let(nil, T.nilable(T::Hash[String, T.untyped])) @built_on = T.let(nil, T.nilable(T::Hash[String, T.untyped]))
@runtime_dependencies = T.let(nil, T.nilable(T::Array[T.untyped])) @runtime_dependencies = T.let(nil, T.nilable(T::Array[T.untyped]))
attributes.each { |key, value| instance_variable_set(:"@#{key}", value) } attributes.each do |key, value|
case key.to_sym
when :changed_files
@changed_files = value&.map { |f| Pathname(f) }
else
instance_variable_set(:"@#{key}", value)
end
end
end end
# Instantiates a {Tab} for a new installation of a formula or cask. # Instantiates a {Tab} for a new installation of a formula or cask.
@ -166,15 +174,18 @@ class Tab < AbstractTab
# @api internal # @api internal
attr_accessor :poured_from_bottle attr_accessor :poured_from_bottle
attr_accessor :built_as_bottle, :changed_files, :stdlib, :aliases attr_accessor :built_as_bottle, :stdlib, :aliases
attr_writer :used_options, :unused_options, :compiler, :source_modified_time attr_writer :used_options, :unused_options, :compiler, :source_modified_time
attr_reader :tapped_from attr_reader :tapped_from
sig { params(attributes: T::Hash[String, T.untyped]).void } sig { returns(T.nilable(T::Array[Pathname])) }
attr_accessor :changed_files
sig { params(attributes: T.any(T::Hash[String, T.untyped], T::Hash[Symbol, T.untyped])).void }
def initialize(attributes = {}) def initialize(attributes = {})
@poured_from_bottle = T.let(nil, T.nilable(T::Boolean)) @poured_from_bottle = T.let(nil, T.nilable(T::Boolean))
@built_as_bottle = T.let(nil, T.nilable(T::Boolean)) @built_as_bottle = T.let(nil, T.nilable(T::Boolean))
@changed_files = T.let(nil, T.nilable(T::Array[Pathname])) @changed_files = nil
@stdlib = T.let(nil, T.nilable(String)) @stdlib = T.let(nil, T.nilable(String))
@aliases = T.let(nil, T.nilable(T::Array[String])) @aliases = T.let(nil, T.nilable(T::Array[String]))
@used_options = T.let(nil, T.nilable(T::Array[String])) @used_options = T.let(nil, T.nilable(T::Array[String]))

View File

@ -83,7 +83,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
expect(hub.select_formula_or_cask(:A)).to be_empty expect(hub.select_formula_or_cask(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).to be_empty expect(hub.select_formula_or_cask(:D)).to be_empty
expect(hub.select_formula_or_cask(:R)).to eq([["cv", "progress"]]) expect(hub.instance_variable_get(:@hash)[:R]).to eq([["cv", "progress"]])
end end
context "when updating a Tap other than the core Tap" do context "when updating a Tap other than the core Tap" do
@ -102,7 +102,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
expect(hub.select_formula_or_cask(:A)).to be_empty expect(hub.select_formula_or_cask(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).to be_empty expect(hub.select_formula_or_cask(:D)).to be_empty
expect(hub.select_formula_or_cask(:R)).to be_empty expect(hub.instance_variable_get(:@hash)[:R]).to be_nil
end end
specify "with renamed Formula and restructured Tap" do specify "with renamed Formula and restructured Tap" do
@ -111,7 +111,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
expect(hub.select_formula_or_cask(:A)).to be_empty expect(hub.select_formula_or_cask(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).to be_empty expect(hub.select_formula_or_cask(:D)).to be_empty
expect(hub.select_formula_or_cask(:R)).to eq([%w[foo/bar/xchat foo/bar/xchat2]]) expect(hub.instance_variable_get(:@hash)[:R]).to eq([%w[foo/bar/xchat foo/bar/xchat2]])
end end
specify "with simulated 'homebrew/php' restructuring" do specify "with simulated 'homebrew/php' restructuring" do
@ -119,7 +119,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
expect(hub.select_formula_or_cask(:A)).to be_empty expect(hub.select_formula_or_cask(:A)).to be_empty
expect(hub.select_formula_or_cask(:D)).to be_empty expect(hub.select_formula_or_cask(:D)).to be_empty
expect(hub.select_formula_or_cask(:R)).to be_empty expect(hub.instance_variable_get(:@hash)[:R]).to be_nil
end end
specify "with Formula changes" do specify "with Formula changes" do
@ -127,7 +127,7 @@ RSpec.describe Homebrew::Cmd::UpdateReport do
expect(hub.select_formula_or_cask(:A)).to eq(%w[foo/bar/lua]) expect(hub.select_formula_or_cask(:A)).to eq(%w[foo/bar/lua])
expect(hub.select_formula_or_cask(:M)).to eq(%w[foo/bar/git]) expect(hub.select_formula_or_cask(:M)).to eq(%w[foo/bar/git])
expect(hub.select_formula_or_cask(:D)).to be_empty expect(hub.instance_variable_get(:@hash)[:R]).to be_nil
end end
end end
end end

View File

@ -173,7 +173,7 @@ RSpec.describe CurlDownloadStrategy do
hash_including(args: array_including_cons("#{artifact_domain}/#{resource_path}")), hash_including(args: array_including_cons("#{artifact_domain}/#{resource_path}")),
) )
.at_least(:once) .at_least(:once)
.and_return(SystemCommand::Result.new(["curl"], [""], status, secrets: [])) .and_return(SystemCommand::Result.new(["curl"], [[:stdout, ""]], status, secrets: []))
strategy.fetch strategy.fetch
end end
@ -191,7 +191,7 @@ RSpec.describe CurlDownloadStrategy do
hash_including(args: array_including_cons("#{artifact_domain}/#{resource_path}")), hash_including(args: array_including_cons("#{artifact_domain}/#{resource_path}")),
) )
.at_least(:once) .at_least(:once)
.and_return(SystemCommand::Result.new(["curl"], [""], status, secrets: [])) .and_return(SystemCommand::Result.new(["curl"], [[:stdout, ""]], status, secrets: []))
strategy.fetch strategy.fetch
end end

View File

@ -110,7 +110,7 @@ RSpec.describe Homebrew::Livecheck::Strategy::HeaderMatch do
it "errors on an invalid return type from a block" do it "errors on an invalid return type from a block" do
expect { header_match.versions_from_headers(headers) { 123 } } expect { header_match.versions_from_headers(headers) { 123 } }
.to raise_error(TypeError, Homebrew::Livecheck::Strategy::INVALID_BLOCK_RETURN_VALUE_MSG) .to raise_error(TypeError, /Parameter 'headers': Expected type T::Hash\[String, String\]/o)
end end
end end
end end

View File

@ -87,8 +87,9 @@ RSpec.describe Homebrew::Services::Cli do
describe "#run" do describe "#run" do
it "checks missing file causes error" do it "checks missing file causes error" do
expect(Homebrew::Services::System).not_to receive(:root?) expect(Homebrew::Services::System).not_to receive(:root?)
service = instance_double(Homebrew::Services::FormulaWrapper, name: "service_name")
expect do expect do
services_cli.start(["service_name"], "/non/existent/path") services_cli.start([service], "/non/existent/path")
end.to raise_error(UsageError, "Invalid usage: Provided service file does not exist.") end.to raise_error(UsageError, "Invalid usage: Provided service file does not exist.")
end end
@ -110,8 +111,9 @@ RSpec.describe Homebrew::Services::Cli do
describe "#start" do describe "#start" do
it "checks missing file causes error" do it "checks missing file causes error" do
expect(Homebrew::Services::System).not_to receive(:root?) expect(Homebrew::Services::System).not_to receive(:root?)
service = instance_double(Homebrew::Services::FormulaWrapper, name: "service_name")
expect do expect do
services_cli.start(["service_name"], "/hfdkjshksdjhfkjsdhf/fdsjghsdkjhb") services_cli.start([service], "/hfdkjshksdjhfkjsdhf/fdsjghsdkjhb")
end.to raise_error(UsageError, "Invalid usage: Provided service file does not exist.") end.to raise_error(UsageError, "Invalid usage: Provided service file does not exist.")
end end

View File

@ -23,7 +23,7 @@ RSpec.describe Homebrew::Services::Commands::Info do
it "succeeds with items" do it "succeeds with items" do
out = "service ()\nRunning: true\nLoaded: true\nSchedulable: false\n" out = "service ()\nRunning: true\nLoaded: true\nSchedulable: false\n"
formula = { formula_wrapper = instance_double(Homebrew::Services::FormulaWrapper, to_hash: {
name: "service", name: "service",
user: "user", user: "user",
status: :started, status: :started,
@ -31,9 +31,9 @@ RSpec.describe Homebrew::Services::Commands::Info do
running: true, running: true,
loaded: true, loaded: true,
schedulable: false, schedulable: false,
} })
expect do expect do
described_class.run([formula], verbose: false, json: false) described_class.run([formula_wrapper], verbose: false, json: false)
end.to output(out).to_stdout end.to output(out).to_stdout
end end
@ -48,8 +48,9 @@ RSpec.describe Homebrew::Services::Commands::Info do
schedulable: false, schedulable: false,
} }
out = "#{JSON.pretty_generate([formula])}\n" out = "#{JSON.pretty_generate([formula])}\n"
formula_wrapper = instance_double(Homebrew::Services::FormulaWrapper, to_hash: formula)
expect do expect do
described_class.run([formula], verbose: false, json: true) described_class.run([formula_wrapper], verbose: false, json: true)
end.to output(out).to_stdout end.to output(out).to_stdout
end end
end end

View File

@ -275,7 +275,7 @@ RSpec.describe SystemCommand do
FileUtils.chmod "+x", path/"tool" FileUtils.chmod "+x", path/"tool"
expect(described_class.run("tool", env: { "PATH" => path }).stdout).to include "Hello, world!" expect(described_class.run("tool", env: { "PATH" => path.to_s }).stdout).to include "Hello, world!"
end end
end end

View File

@ -241,7 +241,7 @@ RSpec.describe Tab do
tab = described_class.from_file(path) tab = described_class.from_file(path)
source_path = "/usr/local/Library/Taps/homebrew/homebrew-core/Formula/foo.rb" source_path = "/usr/local/Library/Taps/homebrew/homebrew-core/Formula/foo.rb"
runtime_dependencies = [{ "full_name" => "foo", "version" => "1.0" }] runtime_dependencies = [{ "full_name" => "foo", "version" => "1.0" }]
changed_files = %w[INSTALL_RECEIPT.json bin/foo] changed_files = %w[INSTALL_RECEIPT.json bin/foo].map { Pathname.new(_1) }
expect(tab.used_options.sort).to eq(used_options.sort) expect(tab.used_options.sort).to eq(used_options.sort)
expect(tab.unused_options.sort).to eq(unused_options.sort) expect(tab.unused_options.sort).to eq(unused_options.sort)
@ -271,7 +271,7 @@ RSpec.describe Tab do
tab = described_class.from_file_content(path.read, path) tab = described_class.from_file_content(path.read, path)
source_path = "/usr/local/Library/Taps/homebrew/homebrew-core/Formula/foo.rb" source_path = "/usr/local/Library/Taps/homebrew/homebrew-core/Formula/foo.rb"
runtime_dependencies = [{ "full_name" => "foo", "version" => "1.0" }] runtime_dependencies = [{ "full_name" => "foo", "version" => "1.0" }]
changed_files = %w[INSTALL_RECEIPT.json bin/foo] changed_files = %w[INSTALL_RECEIPT.json bin/foo].map { Pathname.new(_1) }
expect(tab.used_options.sort).to eq(used_options.sort) expect(tab.used_options.sort).to eq(used_options.sort)
expect(tab.unused_options.sort).to eq(unused_options.sort) expect(tab.unused_options.sort).to eq(unused_options.sort)

View File

@ -184,7 +184,7 @@ RSpec.describe Utils::Analytics do
end end
specify "::table_output" do specify "::table_output" do
results = { ack: 10, wget: 100 } results = { "ack" => 10, "wget" => 100 }
expect { described_class.table_output("install", "30", results) } expect { described_class.table_output("install", "30", results) }
.to output(/110 | 100.00%/).to_stdout .to output(/110 | 100.00%/).to_stdout
.and not_to_output.to_stderr .and not_to_output.to_stderr

View File

@ -86,7 +86,7 @@ RSpec.describe Utils::Inreplace do
it "raises error if there is no old value" do it "raises error if there is no old value" do
expect do expect do
described_class.inreplace_pairs(file.path, [[nil, "f"]]) described_class.inreplace_pairs(file.path, [[nil, "f"]])
end.to raise_error(Utils::Inreplace::Error) end.to raise_error(TypeError)
end end
it "substitutes returned string but not file when `read_only_run: true`" do it "substitutes returned string but not file when `read_only_run: true`" do

View File

@ -36,7 +36,7 @@ module UnpackStrategy
quiet_flags = verbose ? [] : ["-qq"] quiet_flags = verbose ? [] : ["-qq"]
result = system_command! "unzip", result = system_command! "unzip",
args: [*quiet_flags, "-o", path, "-d", unpack_dir], args: [*quiet_flags, "-o", path, "-d", unpack_dir],
env: { "PATH" => PATH.new(unzip&.opt_bin, ENV.fetch("PATH")) }, env: { "PATH" => PATH.new(unzip&.opt_bin, ENV.fetch("PATH")).to_s },
verbose:, verbose:,
print_stderr: false print_stderr: false

View File

@ -360,7 +360,7 @@ module Utils
nil nil
end end
sig { returns(T::Hash[Symbol, String]) } sig { returns(T::Hash[Symbol, T.any(T::Boolean, String)]) }
def default_package_tags def default_package_tags
cache[:default_package_tags] ||= begin cache[:default_package_tags] ||= begin
# Only display default prefixes to reduce cardinality and improve privacy # Only display default prefixes to reduce cardinality and improve privacy