diff --git a/Library/Homebrew/cask/audit.rb b/Library/Homebrew/cask/audit.rb index 1d6fa742e4df8..2c935ae351bb5 100644 --- a/Library/Homebrew/cask/audit.rb +++ b/Library/Homebrew/cask/audit.rb @@ -754,7 +754,7 @@ def cask_plist_min_os def audit_github_prerelease_version odebug "Auditing GitHub prerelease" user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online? - return if user.nil? + return if user.nil? || repo.nil? tag = SharedAudits.github_tag_from_url(cask.url) tag ||= cask.version @@ -765,7 +765,7 @@ def audit_github_prerelease_version sig { void } def audit_gitlab_prerelease_version user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online? - return if user.nil? + return if user.nil? || repo.nil? odebug "Auditing GitLab prerelease" @@ -781,7 +781,7 @@ def audit_github_repository_archived return if cask.deprecated? || cask.disabled? user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online? - return if user.nil? + return if user.nil? || repo.nil? metadata = SharedAudits.github_repo_data(user, repo) return if metadata.nil? @@ -795,7 +795,7 @@ def audit_gitlab_repository_archived return if cask.deprecated? || cask.disabled? user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online? - return if user.nil? + return if user.nil? || repo.nil? odebug "Auditing GitLab repo archived" @@ -810,7 +810,7 @@ def audit_github_repository return unless new_cask? user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) - return if user.nil? + return if user.nil? || repo.nil? odebug "Auditing GitHub repo" @@ -823,7 +823,7 @@ def audit_gitlab_repository return unless new_cask? user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) - return if user.nil? + return if user.nil? || repo.nil? odebug "Auditing GitLab repo" @@ -836,7 +836,7 @@ def audit_bitbucket_repository return unless new_cask? user, repo = get_repo_data(%r{https?://bitbucket\.org/([^/]+)/([^/]+)/?.*}) - return if user.nil? + return if user.nil? || repo.nil? odebug "Auditing Bitbucket repo" diff --git a/Library/Homebrew/extend/os/mac/utils/bottles.rb b/Library/Homebrew/extend/os/mac/utils/bottles.rb index a06d0db5da9ca..754aeaec64ef7 100644 --- a/Library/Homebrew/extend/os/mac/utils/bottles.rb +++ b/Library/Homebrew/extend/os/mac/utils/bottles.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true module Utils @@ -21,6 +21,7 @@ class Collector alias generic_find_matching_tag find_matching_tag + sig { params(tag: Utils::Bottles::Tag, no_older_versions: T::Boolean).returns(T.nilable(Utils::Bottles::Tag)) } def find_matching_tag(tag, no_older_versions: false) # Used primarily by developers testing beta macOS releases. if no_older_versions || @@ -35,6 +36,7 @@ def find_matching_tag(tag, no_older_versions: false) end # Find a bottle built for a previous version of macOS. + sig { params(tag: Utils::Bottles::Tag).returns(T.nilable(Utils::Bottles::Tag)) } def find_older_compatible_tag(tag) tag_version = begin tag.to_macos_version diff --git a/Library/Homebrew/formula_auditor.rb b/Library/Homebrew/formula_auditor.rb index f0c63a2e51426..3323313936dac 100644 --- a/Library/Homebrew/formula_auditor.rb +++ b/Library/Homebrew/formula_auditor.rb @@ -582,15 +582,13 @@ def audit_eol metadata = SharedAudits.eol_data(name, formula.version.major) metadata ||= SharedAudits.eol_data(name, formula.version.major_minor) - return if metadata.blank? || metadata["eol"] == false + return if metadata.blank? || (eol_date = metadata["eol"]).blank? - see_url = "see #{Formatter.url("https://endoflife.date/#{name}")}" - if metadata["eol"] == true - problem "Product is EOL, #{see_url}" - return - end + message = "Product is EOL" + message += " since #{eol_date}" if Date.parse(eol_date.to_s) <= Date.today + message += ", see #{Formatter.url("https://endoflife.date/#{name}")}" - problem "Product is EOL since #{metadata["eol"]}, #{see_url}" if Date.parse(metadata["eol"]) <= Date.today + problem message end def audit_wayback_url @@ -778,8 +776,8 @@ def audit_specs problem "#{stable.version} is a development release" when %r{https?://gitlab\.com/([\w-]+)/([\w-]+)} - owner = Regexp.last_match(1) - repo = Regexp.last_match(2) + owner = T.must(Regexp.last_match(1)) + repo = T.must(Regexp.last_match(2)) tag = SharedAudits.gitlab_tag_from_url(url) tag ||= stable.specs[:tag] @@ -790,8 +788,8 @@ def audit_specs problem error if error end when %r{^https://github.com/([\w-]+)/([\w-]+)} - owner = Regexp.last_match(1) - repo = Regexp.last_match(2) + owner = T.must(Regexp.last_match(1)) + repo = T.must(Regexp.last_match(2)) tag = SharedAudits.github_tag_from_url(url) tag ||= formula.stable.specs[:tag] diff --git a/Library/Homebrew/metafiles.rb b/Library/Homebrew/metafiles.rb index 9f3a74239f401..a321874cf2007 100644 --- a/Library/Homebrew/metafiles.rb +++ b/Library/Homebrew/metafiles.rb @@ -1,27 +1,33 @@ -# typed: true +# typed: strict # frozen_string_literal: true # Helper for checking if a file is considered a metadata file. module Metafiles - LICENSES = Set.new(%w[copying copyright license licence]).freeze + LICENSES = T.let(Set.new(%w[copying copyright license licence]).freeze, T::Set[String]) # {https://github.com/github/markup#markups} - EXTENSIONS = Set.new(%w[ + EXTENSIONS = T.let(Set.new(%w[ .adoc .asc .asciidoc .creole .html .markdown .md .mdown .mediawiki .mkdn .org .pod .rdoc .rst .rtf .textile .txt .wiki - ]).freeze - BASENAMES = Set.new(%w[about authors changelog changes history news notes notice readme todo]).freeze + ]).freeze, T::Set[String]) + BASENAMES = T.let(Set.new(%w[ + about authors changelog changes history news notes notice readme todo + ]).freeze, T::Set[String]) module_function + sig { params(file: String).returns(T::Boolean) } def list?(file) return false if %w[.DS_Store INSTALL_RECEIPT.json].include?(file) !copy?(file) end + sig { params(file: String).returns(T::Boolean) } def copy?(file) file = file.downcase - return true if LICENSES.include? file.split(/\.|-/).first + license = file.split(/\.|-/).first + return false unless license + return true if LICENSES.include?(license) ext = File.extname(file) file = File.basename(file, ext) if EXTENSIONS.include?(ext) diff --git a/Library/Homebrew/missing_formula.rb b/Library/Homebrew/missing_formula.rb index 7a49643accd2c..4b9dbdaee4309 100644 --- a/Library/Homebrew/missing_formula.rb +++ b/Library/Homebrew/missing_formula.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "formulary" @@ -7,11 +7,13 @@ module Homebrew # Helper module for checking if there is a reason a formula is missing. module MissingFormula class << self + sig { params(name: String, silent: T::Boolean, show_info: T::Boolean).returns(T.nilable(String)) } def reason(name, silent: false, show_info: false) cask_reason(name, silent:, show_info:) || disallowed_reason(name) || tap_migration_reason(name) || deleted_reason(name, silent:) end + sig { params(name: String).returns(T.nilable(String)) } def disallowed_reason(name) case name.downcase when "gem", /^rubygems?$/ then <<~EOS @@ -93,6 +95,7 @@ def disallowed_reason(name) end alias generic_disallowed_reason disallowed_reason + sig { params(name: String).returns(T.nilable(String)) } def tap_migration_reason(name) message = T.let(nil, T.nilable(String)) @@ -127,6 +130,7 @@ def tap_migration_reason(name) message end + sig { params(name: String, silent: T::Boolean).returns(T.nilable(String)) } def deleted_reason(name, silent: false) path = Formulary.path name return if File.exist? path diff --git a/Library/Homebrew/readall.rb b/Library/Homebrew/readall.rb index c20251fbb1811..8966214a5cf0c 100644 --- a/Library/Homebrew/readall.rb +++ b/Library/Homebrew/readall.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "formula" @@ -16,6 +16,7 @@ module Readall private_class_method :cache + sig { params(ruby_files: T::Array[Pathname]).returns(T::Boolean) } def self.valid_ruby_syntax?(ruby_files) failed = T.let(false, T::Boolean) ruby_files.each do |ruby_file| @@ -25,6 +26,7 @@ def self.valid_ruby_syntax?(ruby_files) !failed end + sig { params(alias_dir: Pathname, formula_dir: Pathname).returns(T::Boolean) } def self.valid_aliases?(alias_dir, formula_dir) return true unless alias_dir.directory? @@ -46,6 +48,7 @@ def self.valid_aliases?(alias_dir, formula_dir) !failed end + sig { params(tap: Tap, bottle_tag: T.nilable(Utils::Bottles::Tag)).returns(T::Boolean) } def self.valid_formulae?(tap, bottle_tag: nil) cache[:valid_formulae] ||= {} @@ -55,7 +58,7 @@ def self.valid_formulae?(tap, bottle_tag: nil) next if valid == true || valid&.include?(bottle_tag) formula_name = file.basename(".rb").to_s - formula_contents = file.read(encoding: "UTF-8") + formula_contents = file.read.force_encoding("UTF-8") readall_namespace = "ReadallNamespace" readall_formula_class = Formulary.load_formula(formula_name, file, formula_contents, readall_namespace, @@ -79,10 +82,16 @@ def self.valid_formulae?(tap, bottle_tag: nil) success end + sig { params(_tap: Tap, os_name: T.nilable(Symbol), arch: T.nilable(Symbol)).returns(T::Boolean) } def self.valid_casks?(_tap, os_name: nil, arch: nil) true end + sig { + params( + tap: Tap, aliases: T::Boolean, no_simulate: T::Boolean, os_arch_combinations: T::Array[T::Array[String]], + ).returns(T::Boolean) + } def self.valid_tap?(tap, aliases: false, no_simulate: false, os_arch_combinations: OnSystem::ALL_OS_ARCH_COMBINATIONS) success = true @@ -110,6 +119,7 @@ def self.valid_tap?(tap, aliases: false, no_simulate: false, success end + sig { params(filename: Pathname).returns(T::Boolean) } private_class_method def self.syntax_errors_or_warnings?(filename) # Retrieve messages about syntax errors/warnings printed to `$stderr`. _, err, status = system_command(RUBY_PATH, args: ["-c", "-w", filename], print_stderr: false) diff --git a/Library/Homebrew/unlink.rb b/Library/Homebrew/unlink.rb index 73ce64081690d..228cd8736cc27 100644 --- a/Library/Homebrew/unlink.rb +++ b/Library/Homebrew/unlink.rb @@ -1,9 +1,10 @@ -# typed: true +# typed: strict # frozen_string_literal: true module Homebrew # Provides helper methods for unlinking formulae and kegs with consistent output. module Unlink + sig { params(formula: Formula, verbose: T::Boolean).void } def self.unlink_versioned_formulae(formula, verbose: false) formula.versioned_formulae .select(&:keg_only?) @@ -15,6 +16,7 @@ def self.unlink_versioned_formulae(formula, verbose: false) end end + sig { params(keg: Keg, dry_run: T::Boolean, verbose: T::Boolean).void } def self.unlink(keg, dry_run: false, verbose: false) options = { dry_run:, verbose: } diff --git a/Library/Homebrew/utils/link.rb b/Library/Homebrew/utils/link.rb index 09e56d2696cf7..01a5211b3ec7a 100644 --- a/Library/Homebrew/utils/link.rb +++ b/Library/Homebrew/utils/link.rb @@ -1,9 +1,10 @@ -# typed: true +# typed: strict # frozen_string_literal: true module Utils # Helper functions for creating symlinks. module Link + sig { params(src_dir: Pathname, dst_dir: Pathname, command: String, link_dir: T::Boolean).void } def self.link_src_dst_dirs(src_dir, dst_dir, command, link_dir: false) return unless src_dir.exist? @@ -38,6 +39,7 @@ def self.link_src_dst_dirs(src_dir, dst_dir, command, link_dir: false) end private_class_method :link_src_dst_dirs + sig { params(src_dir: Pathname, dst_dir: Pathname, unlink_dir: T::Boolean).void } def self.unlink_src_dst_dirs(src_dir, dst_dir, unlink_dir: false) return unless src_dir.exist? @@ -52,26 +54,31 @@ def self.unlink_src_dst_dirs(src_dir, dst_dir, unlink_dir: false) end private_class_method :unlink_src_dst_dirs + sig { params(path: Pathname, command: String).void } def self.link_manpages(path, command) link_src_dst_dirs(path/"manpages", HOMEBREW_PREFIX/"share/man/man1", command) end + sig { params(path: Pathname).void } def self.unlink_manpages(path) unlink_src_dst_dirs(path/"manpages", HOMEBREW_PREFIX/"share/man/man1") end + sig { params(path: Pathname, command: String).void } def self.link_completions(path, command) link_src_dst_dirs(path/"completions/bash", HOMEBREW_PREFIX/"etc/bash_completion.d", command) link_src_dst_dirs(path/"completions/zsh", HOMEBREW_PREFIX/"share/zsh/site-functions", command) link_src_dst_dirs(path/"completions/fish", HOMEBREW_PREFIX/"share/fish/vendor_completions.d", command) end + sig { params(path: Pathname).void } def self.unlink_completions(path) unlink_src_dst_dirs(path/"completions/bash", HOMEBREW_PREFIX/"etc/bash_completion.d") unlink_src_dst_dirs(path/"completions/zsh", HOMEBREW_PREFIX/"share/zsh/site-functions") unlink_src_dst_dirs(path/"completions/fish", HOMEBREW_PREFIX/"share/fish/vendor_completions.d") end + sig { params(path: Pathname, command: String).void } def self.link_docs(path, command) link_src_dst_dirs(path/"docs", HOMEBREW_PREFIX/"share/doc/homebrew", command, link_dir: true) end diff --git a/Library/Homebrew/utils/service.rb b/Library/Homebrew/utils/service.rb index ceb8cd36e2bf2..3c13d75c70ce0 100644 --- a/Library/Homebrew/utils/service.rb +++ b/Library/Homebrew/utils/service.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true module Utils @@ -27,7 +27,7 @@ def self.launchctl return @launchctl if defined? @launchctl return if ENV["HOMEBREW_TEST_GENERIC_OS"] - @launchctl = which("launchctl") + @launchctl = T.let(which("launchctl"), T.nilable(Pathname)) end # Path to systemctl binary. @@ -36,7 +36,7 @@ def self.systemctl return @systemctl if defined? @systemctl return if ENV["HOMEBREW_TEST_GENERIC_OS"] - @systemctl = which("systemctl") + @systemctl = T.let(which("systemctl"), T.nilable(Pathname)) end sig { returns(T::Boolean) } diff --git a/Library/Homebrew/utils/shared_audits.rb b/Library/Homebrew/utils/shared_audits.rb index 3329ea1797fb4..b7cd07a4c496d 100644 --- a/Library/Homebrew/utils/shared_audits.rb +++ b/Library/Homebrew/utils/shared_audits.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "utils/curl" @@ -10,8 +10,9 @@ module SharedAudits module_function + sig { params(product: String, cycle: String).returns(T.nilable(T::Hash[String, T::Hash[Symbol, T.untyped]])) } def eol_data(product, cycle) - @eol_data ||= {} + @eol_data ||= T.let({}, T.nilable(T::Hash[String, T::Hash[String, T.untyped]])) @eol_data["#{product}/#{cycle}"] ||= begin out, _, status = Utils::Curl.curl_output("--location", "https://endoflife.date/api/#{product}/#{cycle}.json") json = JSON.parse(out) if status.success? @@ -20,8 +21,9 @@ def eol_data(product, cycle) end end + sig { params(user: String, repo: String).returns(T.nilable(T::Hash[String, T.untyped])) } def github_repo_data(user, repo) - @github_repo_data ||= {} + @github_repo_data ||= T.let({}, T.nilable(T::Hash[String, T.untyped])) @github_repo_data["#{user}/#{repo}"] ||= GitHub.repository(user, repo) @github_repo_data["#{user}/#{repo}"] @@ -31,10 +33,11 @@ def github_repo_data(user, repo) raise unless e.message.match?(GitHub::API::GITHUB_IP_ALLOWLIST_ERROR) end + sig { params(user: String, repo: String, tag: String).returns(T.nilable(T::Hash[String, T.untyped])) } def github_release_data(user, repo, tag) id = "#{user}/#{repo}/#{tag}" url = "#{GitHub::API_URL}/repos/#{user}/#{repo}/releases/tags/#{tag}" - @github_release_data ||= {} + @github_release_data ||= T.let({}, T.nilable(T::Hash[String, T.untyped])) @github_release_data[id] ||= GitHub::API.open_rest(url) @github_release_data[id] @@ -44,6 +47,13 @@ def github_release_data(user, repo, tag) raise unless e.message.match?(GitHub::API::GITHUB_IP_ALLOWLIST_ERROR) end + sig { + params( + user: String, repo: String, tag: String, formula: T.nilable(Formula), cask: T.nilable(Cask::Cask), + ).returns( + T.nilable(String), + ) + } def github_release(user, repo, tag, formula: nil, cask: nil) release = github_release_data(user, repo, tag) return unless release @@ -63,8 +73,9 @@ def github_release(user, repo, tag, formula: nil, cask: nil) "#{tag} is a GitHub draft." if release["draft"] end + sig { params(user: String, repo: String).returns(T.nilable(T::Hash[String, T.untyped])) } def gitlab_repo_data(user, repo) - @gitlab_repo_data ||= {} + @gitlab_repo_data ||= T.let({}, T.nilable(T::Hash[String, T.untyped])) @gitlab_repo_data["#{user}/#{repo}"] ||= begin out, _, status = Utils::Curl.curl_output("https://gitlab.com/api/v4/projects/#{user}%2F#{repo}") json = JSON.parse(out) if status.success? @@ -73,9 +84,10 @@ def gitlab_repo_data(user, repo) end end + sig { params(user: String, repo: String, tag: String).returns(T.nilable(T::Hash[String, T.untyped])) } def gitlab_release_data(user, repo, tag) id = "#{user}/#{repo}/#{tag}" - @gitlab_release_data ||= {} + @gitlab_release_data ||= T.let({}, T.nilable(T::Hash[String, T.untyped])) @gitlab_release_data[id] ||= begin out, _, status = Utils::Curl.curl_output( "https://gitlab.com/api/v4/projects/#{user}%2F#{repo}/releases/#{tag}", "--fail" @@ -84,6 +96,13 @@ def gitlab_release_data(user, repo, tag) end end + sig { + params( + user: String, repo: String, tag: String, formula: T.nilable(Formula), cask: T.nilable(Cask::Cask), + ).returns( + T.nilable(String), + ) + } def gitlab_release(user, repo, tag, formula: nil, cask: nil) release = gitlab_release_data(user, repo, tag) return unless release @@ -100,6 +119,7 @@ def gitlab_release(user, repo, tag, formula: nil, cask: nil) "#{tag} is a GitLab pre-release." end + sig { params(user: String, repo: String).returns(T.nilable(String)) } def github(user, repo) metadata = github_repo_data(user, repo) @@ -117,6 +137,7 @@ def github(user, repo) "GitHub repository too new (<30 days old)" end + sig { params(user: String, repo: String).returns(T.nilable(String)) } def gitlab(user, repo) metadata = gitlab_repo_data(user, repo) @@ -132,6 +153,7 @@ def gitlab(user, repo) "GitLab repository too new (<30 days old)" end + sig { params(user: String, repo: String).returns(T.nilable(String)) } def bitbucket(user, repo) api_url = "https://api.bitbucket.org/2.0/repositories/#{user}/#{repo}" out, _, status = Utils::Curl.curl_output("--request", "GET", api_url) @@ -163,6 +185,7 @@ def bitbucket(user, repo) "Bitbucket repository not notable enough (<30 forks and <75 watchers)" end + sig { params(url: String).returns(T.nilable(String)) } def github_tag_from_url(url) url = url.to_s tag = url.match(%r{^https://github\.com/[\w-]+/[\w-]+/archive/refs/tags/([^/]+)\.(tar\.gz|zip)$}) @@ -174,6 +197,7 @@ def github_tag_from_url(url) tag end + sig { params(url: String).returns(T.nilable(String)) } def gitlab_tag_from_url(url) url = url.to_s url.match(%r{^https://gitlab\.com/[\w-]+/[\w-]+/-/archive/([^/]+)/}) diff --git a/Library/Homebrew/utils/svn.rb b/Library/Homebrew/utils/svn.rb index 93f6b100a976d..049e95e7985b5 100644 --- a/Library/Homebrew/utils/svn.rb +++ b/Library/Homebrew/utils/svn.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "system_command" @@ -19,7 +19,7 @@ def version return @version if defined?(@version) stdout, _, status = system_command(HOMEBREW_SHIMS_PATH/"shared/svn", args: ["--version"], print_stderr: false) - @version = status.success? ? stdout.chomp[/svn, version (\d+(?:\.\d+)*)/, 1] : nil + @version = T.let(status.success? ? stdout.chomp[/svn, version (\d+(?:\.\d+)*)/, 1] : nil, T.nilable(String)) end sig { params(url: String).returns(T::Boolean) } @@ -34,7 +34,7 @@ def remote_exists?(url) system_command("svn", args: args.concat(invalid_cert_flags), print_stderr: false).success? end - sig { returns(Array) } + sig { returns(T::Array[String]) } def invalid_cert_flags opoo "Ignoring Subversion certificate errors!" args = ["--non-interactive", "--trust-server-cert"] @@ -44,6 +44,7 @@ def invalid_cert_flags args end + sig { void } def clear_version_cache remove_instance_variable(:@version) if defined?(@version) end diff --git a/Library/Homebrew/utils/user.rb b/Library/Homebrew/utils/user.rb index 09be3d770774e..72339509f6860 100644 --- a/Library/Homebrew/utils/user.rb +++ b/Library/Homebrew/utils/user.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "delegate" @@ -29,6 +29,6 @@ def self.current pwuid = Etc.getpwuid(Process.euid) return if pwuid.nil? - @current = new(pwuid.name) + @current = T.let(new(pwuid.name), T.nilable(T.attached_class)) end end