Merge pull request #13813 from Homebrew/revert-13746-chore/cask/cleanup_audit

Revert "Cask: automatically add cask audits"
This commit is contained in:
Mike McQuaid 2022-09-06 12:12:08 +01:00 committed by GitHub
commit ed916a5315
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 314 additions and 369 deletions

View File

@ -24,7 +24,7 @@ module Cask
def initialize(cask, appcast: nil, download: nil, quarantine: nil, def initialize(cask, appcast: nil, download: nil, quarantine: nil,
token_conflicts: nil, online: nil, strict: nil, signing: nil, token_conflicts: nil, online: nil, strict: nil, signing: nil,
new_cask: nil, only: [], except: []) new_cask: nil)
# `new_cask` implies `online`, `token_conflicts`, `strict` and `signing` # `new_cask` implies `online`, `token_conflicts`, `strict` and `signing`
online = new_cask if online.nil? online = new_cask if online.nil?
@ -47,22 +47,44 @@ module Cask
@signing = signing @signing = signing
@new_cask = new_cask @new_cask = new_cask
@token_conflicts = token_conflicts @token_conflicts = token_conflicts
@only = only
@except = except
end end
def run! def run!
only_audits = @only check_denylist
except_audits = @except check_reverse_migration
check_required_stanzas
private_methods.map(&:to_s).grep(/^check_/).each do |audit_method_name| check_version
name = audit_method_name.delete_prefix("check_") check_sha256
next if !only_audits.empty? && only_audits&.exclude?(name) check_desc
next if except_audits&.include?(name) check_url
check_unnecessary_verified
send(audit_method_name) check_missing_verified
end check_no_match
check_generic_artifacts
check_token_valid
check_token_bad_words
check_token_conflicts
check_languages
check_download
check_https_availability
check_single_pre_postflight
check_single_uninstall_zap
check_untrusted_pkg
livecheck_result = check_livecheck_version
check_hosting_with_livecheck(livecheck_result: livecheck_result)
check_appcast_and_livecheck
check_latest_with_appcast_or_livecheck
check_latest_with_auto_updates
check_stanza_requires_uninstall
check_appcast_contains_version
check_gitlab_repository
check_gitlab_repository_archived
check_gitlab_prerelease_version
check_github_repository
check_github_repository_archived
check_github_prerelease_version
check_bitbucket_repository
check_signing
self self
rescue => e rescue => e
odebug e, e.backtrace odebug e, e.backtrace
@ -78,27 +100,10 @@ module Cask
@warnings ||= [] @warnings ||= []
end end
sig { returns(T::Boolean) }
def errors?
errors.any?
end
sig { returns(T::Boolean) }
def warnings?
warnings.any?
end
sig { returns(T::Boolean) }
def success?
!(errors? || warnings?)
end
sig { params(message: String, location: T.nilable(String)).void }
def add_error(message, location: nil) def add_error(message, location: nil)
errors << ({ message: message, location: location }) errors << ({ message: message, location: location })
end end
sig { params(message: String, location: T.nilable(String)).void }
def add_warning(message, location: nil) def add_warning(message, location: nil)
if strict? if strict?
add_error message, location: location add_error message, location: location
@ -107,6 +112,14 @@ module Cask
end end
end end
def errors?
errors.any?
end
def warnings?
warnings.any?
end
def result def result
if errors? if errors?
Formatter.error("failed") Formatter.error("failed")
@ -137,9 +150,12 @@ module Cask
summary.join("\n") summary.join("\n")
end end
def success?
!(errors? || warnings?)
end
private private
sig { void }
def check_untrusted_pkg def check_untrusted_pkg
odebug "Auditing pkg stanza: allow_untrusted" odebug "Auditing pkg stanza: allow_untrusted"
@ -154,7 +170,6 @@ module Cask
add_error "allow_untrusted is not permitted in official Homebrew Cask taps" add_error "allow_untrusted is not permitted in official Homebrew Cask taps"
end end
sig { void }
def check_stanza_requires_uninstall def check_stanza_requires_uninstall
odebug "Auditing stanzas which require an uninstall" odebug "Auditing stanzas which require an uninstall"
@ -164,7 +179,6 @@ module Cask
add_error "installer and pkg stanzas require an uninstall stanza" add_error "installer and pkg stanzas require an uninstall stanza"
end end
sig { void }
def check_single_pre_postflight def check_single_pre_postflight
odebug "Auditing preflight and postflight stanzas" odebug "Auditing preflight and postflight stanzas"
@ -181,7 +195,6 @@ module Cask
add_error "only a single postflight stanza is allowed" add_error "only a single postflight stanza is allowed"
end end
sig { void }
def check_single_uninstall_zap def check_single_uninstall_zap
odebug "Auditing single uninstall_* and zap stanzas" odebug "Auditing single uninstall_* and zap stanzas"
@ -208,7 +221,6 @@ module Cask
add_error "only a single zap stanza is allowed" add_error "only a single zap stanza is allowed"
end end
sig { void }
def check_required_stanzas def check_required_stanzas
odebug "Auditing required stanzas" odebug "Auditing required stanzas"
[:version, :sha256, :url, :homepage].each do |sym| [:version, :sha256, :url, :homepage].each do |sym|
@ -221,66 +233,58 @@ module Cask
add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty? add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty?
end end
sig { void } def check_version
def check_description_present
# Fonts seldom benefit from descriptions and requiring them disproportionately increases the maintenance burden
return if cask.tap == "homebrew/cask-fonts"
add_warning "Cask should have a description. Please add a `desc` stanza." if cask.desc.blank?
end
sig { void }
def check_no_string_version_latest
return unless cask.version return unless cask.version
odebug "Auditing version :latest does not appear as a string ('latest')" check_no_string_version_latest
end
def check_no_string_version_latest
odebug "Verifying version :latest does not appear as a string ('latest')"
return unless cask.version.raw_version == "latest" return unless cask.version.raw_version == "latest"
add_error "you should use version :latest instead of version 'latest'" add_error "you should use version :latest instead of version 'latest'"
end end
sig { void } def check_sha256
def check_sha256_no_check_if_latest
return unless cask.sha256 return unless cask.sha256
odebug "Auditing sha256 :no_check with version :latest" check_sha256_no_check_if_latest
check_sha256_no_check_if_unversioned
check_sha256_actually_256
check_sha256_invalid
end
def check_sha256_no_check_if_latest
odebug "Verifying sha256 :no_check with version :latest"
return unless cask.version.latest? return unless cask.version.latest?
return if cask.sha256 == :no_check return if cask.sha256 == :no_check
add_error "you should use sha256 :no_check when version is :latest" add_error "you should use sha256 :no_check when version is :latest"
end end
sig { void }
def check_sha256_no_check_if_unversioned def check_sha256_no_check_if_unversioned
return unless cask.sha256
return if cask.sha256 == :no_check return if cask.sha256 == :no_check
add_error "Use `sha256 :no_check` when URL is unversioned." if cask.url&.unversioned? add_error "Use `sha256 :no_check` when URL is unversioned." if cask.url&.unversioned?
end end
sig { void }
def check_sha256_actually_256 def check_sha256_actually_256
return unless cask.sha256 odebug "Verifying sha256 string is a legal SHA-256 digest"
odebug "Auditing sha256 string is a legal SHA-256 digest"
return unless cask.sha256.is_a?(Checksum) return unless cask.sha256.is_a?(Checksum)
return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i] return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i]
add_error "sha256 string must be of 64 hexadecimal characters" add_error "sha256 string must be of 64 hexadecimal characters"
end end
sig { void }
def check_sha256_invalid def check_sha256_invalid
return unless cask.sha256 odebug "Verifying sha256 is not a known invalid value"
odebug "Auditing sha256 is not a known invalid value"
empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
return unless cask.sha256 == empty_sha256 return unless cask.sha256 == empty_sha256
add_error "cannot use the sha256 for an empty string: #{empty_sha256}" add_error "cannot use the sha256 for an empty string: #{empty_sha256}"
end end
sig { void }
def check_appcast_and_livecheck def check_appcast_and_livecheck
return unless cask.appcast return unless cask.appcast
@ -291,7 +295,6 @@ module Cask
end end
end end
sig { void }
def check_latest_with_appcast_or_livecheck def check_latest_with_appcast_or_livecheck
return unless cask.version.latest? return unless cask.version.latest?
@ -299,7 +302,6 @@ module Cask
add_error "Casks with a `livecheck` should not use `version :latest`." if cask.livecheckable? add_error "Casks with a `livecheck` should not use `version :latest`." if cask.livecheckable?
end end
sig { void }
def check_latest_with_auto_updates def check_latest_with_auto_updates
return unless cask.version.latest? return unless cask.version.latest?
return unless cask.auto_updates return unless cask.auto_updates
@ -309,8 +311,7 @@ module Cask
LIVECHECK_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#stanza-livecheck" LIVECHECK_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#stanza-livecheck"
sig { params(livecheck_result: T::Boolean).void } def check_hosting_with_livecheck(livecheck_result:)
def check_hosting_with_livecheck(livecheck_result: check_livecheck_version)
return if cask.discontinued? || cask.version.latest? return if cask.discontinued? || cask.version.latest?
return if block_url_offline? || cask.appcast || cask.livecheckable? return if block_url_offline? || cask.appcast || cask.livecheckable?
return if livecheck_result == :auto_detected return if livecheck_result == :auto_detected
@ -329,12 +330,24 @@ module Cask
end end
end end
SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls" def check_desc
return if cask.desc.present?
sig { void } # Fonts seldom benefit from descriptions and requiring them disproportionately increases the maintenance burden
def check_download_url_format return if cask.tap == "homebrew/cask-fonts"
add_warning "Cask should have a description. Please add a `desc` stanza."
end
def check_url
return unless cask.url return unless cask.url
check_download_url_format
end
SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls"
def check_download_url_format
odebug "Auditing URL format" odebug "Auditing URL format"
if bad_sourceforge_url? if bad_sourceforge_url?
add_error "SourceForge URL format incorrect. See #{Formatter.url(SOURCEFORGE_OSDN_REFERENCE_URL)}" add_error "SourceForge URL format incorrect. See #{Formatter.url(SOURCEFORGE_OSDN_REFERENCE_URL)}"
@ -343,9 +356,82 @@ module Cask
end end
end end
def bad_url_format?(regex, valid_formats_array)
return false unless cask.url.to_s.match?(regex)
valid_formats_array.none? { |format| cask.url.to_s =~ format }
end
def bad_sourceforge_url?
bad_url_format?(/sourceforge/,
[
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z},
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)/)},
])
end
def bad_osdn_url?
bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
end
def homepage
URI(cask.homepage.to_s).host
end
def domain
URI(cask.url.to_s).host
end
def url_match_homepage?
host = cask.url.to_s
host_uri = URI(host)
host = if host.match?(/:\d/) && host_uri.port != 80
"#{host_uri.host}:#{host_uri.port}"
else
host_uri.host
end
home = homepage.downcase
if (split_host = host.split(".")).length >= 3
host = split_host[-2..].join(".")
end
if (split_home = homepage.split(".")).length >= 3
home = split_home[-2..].join(".")
end
host == home
end
def strip_url_scheme(url)
url.sub(%r{^[^:/]+://(www\.)?}, "")
end
def url_from_verified
strip_url_scheme(cask.url.verified)
end
def verified_matches_url?
url_domain, url_path = strip_url_scheme(cask.url.to_s).split("/", 2)
verified_domain, verified_path = url_from_verified.split("/", 2)
(url_domain == verified_domain || (verified_domain && url_domain&.end_with?(".#{verified_domain}"))) &&
(!verified_path || url_path&.start_with?(verified_path))
end
def verified_present?
cask.url.verified.present?
end
def file_url?
URI(cask.url.to_s).scheme == "file"
end
def block_url_offline?
return false if online?
cask.url.from_block?
end
VERIFIED_URL_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#when-url-and-homepage-domains-differ-add-verified" VERIFIED_URL_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#when-url-and-homepage-domains-differ-add-verified"
sig { void }
def check_unnecessary_verified def check_unnecessary_verified
return if block_url_offline? return if block_url_offline?
return unless verified_present? return unless verified_present?
@ -357,7 +443,6 @@ module Cask
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}" "See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
end end
sig { void }
def check_missing_verified def check_missing_verified
return if block_url_offline? return if block_url_offline?
return if file_url? return if file_url?
@ -369,7 +454,6 @@ module Cask
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}" "See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
end end
sig { void }
def check_no_match def check_no_match
return if block_url_offline? return if block_url_offline?
return unless verified_present? return unless verified_present?
@ -380,7 +464,6 @@ module Cask
"See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}" "See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
end end
sig { void }
def check_generic_artifacts def check_generic_artifacts
cask.artifacts.select { |a| a.is_a?(Artifact::Artifact) }.each do |artifact| cask.artifacts.select { |a| a.is_a?(Artifact::Artifact) }.each do |artifact|
unless artifact.target.absolute? unless artifact.target.absolute?
@ -389,7 +472,6 @@ module Cask
end end
end end
sig { void }
def check_languages def check_languages
@cask.languages.each do |language| @cask.languages.each do |language|
Locale.parse(language) Locale.parse(language)
@ -398,7 +480,6 @@ module Cask
end end
end end
sig { void }
def check_token_conflicts def check_token_conflicts
return unless token_conflicts? return unless token_conflicts?
return unless core_formula_names.include?(cask.token) return unless core_formula_names.include?(cask.token)
@ -407,7 +488,6 @@ module Cask
"#{Formatter.url(core_formula_url)}" "#{Formatter.url(core_formula_url)}"
end end
sig { void }
def check_token_valid def check_token_valid
add_error "cask token contains non-ascii characters" unless cask.token.ascii_only? add_error "cask token contains non-ascii characters" unless cask.token.ascii_only?
add_error "cask token + should be replaced by -plus-" if cask.token.include? "+" add_error "cask token + should be replaced by -plus-" if cask.token.include? "+"
@ -425,7 +505,6 @@ module Cask
add_error "cask token should not have leading or trailing hyphens" add_error "cask token should not have leading or trailing hyphens"
end end
sig { void }
def check_token_bad_words def check_token_bad_words
return unless new_cask? return unless new_cask?
@ -453,7 +532,19 @@ module Cask
add_warning "cask token mentions framework" add_warning "cask token mentions framework"
end end
sig { void } def core_tap
@core_tap ||= CoreTap.instance
end
def core_formula_names
core_tap.formula_names
end
sig { returns(String) }
def core_formula_url
"#{core_tap.default_remote}/blob/HEAD/Formula/#{cask.token}.rb"
end
def check_download def check_download
return if download.blank? || cask.url.blank? return if download.blank? || cask.url.blank?
@ -463,11 +554,11 @@ module Cask
add_error "download not possible: #{e}" add_error "download not possible: #{e}"
end end
sig { void }
def check_signing def check_signing
return if !signing? || download.blank? || cask.url.blank? return if !signing? || download.blank? || cask.url.blank?
odebug "Auditing signing" odebug "Auditing signing"
odebug cask.artifacts
artifacts = cask.artifacts.select { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) } artifacts = cask.artifacts.select { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) }
return if artifacts.empty? return if artifacts.empty?
@ -507,7 +598,6 @@ module Cask
end end
end end
sig { returns(T.nilable(T.any(T::Boolean, Symbol))) }
def check_livecheck_version def check_livecheck_version
return unless appcast? return unless appcast?
@ -545,7 +635,6 @@ module Cask
false false
end end
sig { void }
def check_appcast_contains_version def check_appcast_contains_version
return unless appcast? return unless appcast?
return if cask.appcast.to_s.empty? return if cask.appcast.to_s.empty?
@ -572,7 +661,6 @@ module Cask
EOS EOS
end end
sig { void }
def check_github_prerelease_version def check_github_prerelease_version
return if cask.tap == "homebrew/cask-versions" return if cask.tap == "homebrew/cask-versions"
@ -586,7 +674,6 @@ module Cask
add_error error if error add_error error if error
end end
sig { void }
def check_gitlab_prerelease_version def check_gitlab_prerelease_version
return if cask.tap == "homebrew/cask-versions" return if cask.tap == "homebrew/cask-versions"
@ -601,7 +688,6 @@ module Cask
add_error error if error add_error error if error
end end
sig { void }
def check_github_repository_archived def check_github_repository_archived
user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online? user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online?
return if user.nil? return if user.nil?
@ -622,7 +708,6 @@ module Cask
end end
end end
sig { void }
def check_gitlab_repository_archived def check_gitlab_repository_archived
user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online? user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online?
return if user.nil? return if user.nil?
@ -643,7 +728,6 @@ module Cask
end end
end end
sig { void }
def check_github_repository def check_github_repository
return unless new_cask? return unless new_cask?
@ -656,7 +740,6 @@ module Cask
add_error error if error add_error error if error
end end
sig { void }
def check_gitlab_repository def check_gitlab_repository
return unless new_cask? return unless new_cask?
@ -669,7 +752,6 @@ module Cask
add_error error if error add_error error if error
end end
sig { void }
def check_bitbucket_repository def check_bitbucket_repository
return unless new_cask? return unless new_cask?
@ -682,62 +764,6 @@ module Cask
add_error error if error add_error error if error
end end
sig { void }
def check_denylist
return unless cask.tap
return unless cask.tap.official?
return unless (reason = Denylist.reason(cask.token))
add_error "#{cask.token} is not allowed: #{reason}"
end
sig { void }
def check_reverse_migration
return unless new_cask?
return unless cask.tap
return unless cask.tap.official?
return unless cask.tap.tap_migrations.key?(cask.token)
add_error "#{cask.token} is listed in tap_migrations.json"
end
sig { void }
def check_https_availability
return unless download
if cask.url && !cask.url.using
validate_url_for_https_availability(cask.url, "binary URL", cask.token, cask.tap,
user_agents: [cask.url.user_agent])
end
if cask.appcast && appcast?
validate_url_for_https_availability(cask.appcast, "appcast URL", cask.token, cask.tap, check_content: true)
end
return unless cask.homepage
validate_url_for_https_availability(cask.homepage, "homepage URL", cask.token, cask.tap,
user_agents: [:browser, :default],
check_content: true,
strict: strict?)
end
sig {
params(url_to_check: T.any(String, URL), url_type: String, cask_token: String, tap: Tap,
options: T.untyped).void
}
def validate_url_for_https_availability(url_to_check, url_type, cask_token, tap, **options)
problem = curl_check_http_content(url_to_check.to_s, url_type, **options)
exception = tap&.audit_exception(:secure_connection_audit_skiplist, cask_token, url_to_check.to_s)
if problem
add_error problem unless exception
elsif exception
add_error "#{url_to_check} is in the secure connection audit skiplist but does not need to be skipped"
end
end
sig { params(regex: String).returns(T.nilable(T::Array[String])) }
def get_repo_data(regex) def get_repo_data(regex)
return unless online? return unless online?
@ -751,105 +777,52 @@ module Cask
[user, repo] [user, repo]
end end
sig { params(regex: T.any(String, Regexp), valid_formats_array: T::Array[String]).returns(T::Boolean) } def check_denylist
def bad_url_format?(regex, valid_formats_array) return unless cask.tap
return false unless cask.url.to_s.match?(regex) return unless cask.tap.official?
return unless (reason = Denylist.reason(cask.token))
valid_formats_array.none? { |format| cask.url.to_s =~ format } add_error "#{cask.token} is not allowed: #{reason}"
end end
sig { returns(T::Boolean) } def check_reverse_migration
def bad_sourceforge_url? return unless new_cask?
bad_url_format?(/sourceforge/, return unless cask.tap
[ return unless cask.tap.official?
%r{\Ahttps://sourceforge\.net/projects/[^/]+/files/latest/download\Z}, return unless cask.tap.tap_migrations.key?(cask.token)
%r{\Ahttps://downloads\.sourceforge\.net/(?!(project|sourceforge)/)},
]) add_error "#{cask.token} is listed in tap_migrations.json"
end end
sig { returns(T::Boolean) } def check_https_availability
def bad_osdn_url? return unless download
bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
if cask.url && !cask.url.using
check_url_for_https_availability(cask.url, "binary URL", cask.token, cask.tap,
user_agents: [cask.url.user_agent])
end end
sig { returns(String) } if cask.appcast && appcast?
def homepage check_url_for_https_availability(cask.appcast, "appcast URL", cask.token, cask.tap, check_content: true)
URI(cask.homepage.to_s).host
end end
sig { returns(String) } return unless cask.homepage
def domain
URI(cask.url.to_s).host check_url_for_https_availability(cask.homepage, "homepage URL", cask.token, cask.tap,
user_agents: [:browser, :default],
check_content: true,
strict: strict?)
end end
sig { returns(T::Boolean) } def check_url_for_https_availability(url_to_check, url_type, cask_token, tap, **options)
def url_match_homepage? problem = curl_check_http_content(url_to_check.to_s, url_type, **options)
host = cask.url.to_s exception = tap&.audit_exception(:secure_connection_audit_skiplist, cask_token, url_to_check.to_s)
host_uri = URI(host)
host = if host.match?(/:\d/) && host_uri.port != 80
"#{host_uri.host}:#{host_uri.port}"
else
host_uri.host
end
home = homepage.downcase
if (split_host = host.split(".")).length >= 3
host = split_host[-2..].join(".")
end
if (split_home = homepage.split(".")).length >= 3
home = split_home[-2..].join(".")
end
host == home
end
sig { params(url: String).returns(String) } if problem
def strip_url_scheme(url) add_error problem unless exception
url.sub(%r{^[^:/]+://(www\.)?}, "") elsif exception
end add_error "#{url_to_check} is in the secure connection audit skiplist but does not need to be skipped"
end
sig { returns(String) }
def url_from_verified
strip_url_scheme(cask.url.verified)
end
sig { returns(T::Boolean) }
def verified_matches_url?
url_domain, url_path = strip_url_scheme(cask.url.to_s).split("/", 2)
verified_domain, verified_path = url_from_verified.split("/", 2)
(url_domain == verified_domain || (verified_domain && url_domain&.end_with?(".#{verified_domain}"))) &&
(!verified_path || url_path&.start_with?(verified_path))
end
sig { returns(T::Boolean) }
def verified_present?
cask.url.verified.present?
end
sig { returns(T::Boolean) }
def file_url?
URI(cask.url.to_s).scheme == "file"
end
sig { returns(T::Boolean) }
def block_url_offline?
return false if online?
cask.url.from_block?
end
sig { returns(Tap) }
def core_tap
@core_tap ||= CoreTap.instance
end
sig { returns(T::Array[String]) }
def core_formula_names
core_tap.formula_names
end
sig { returns(String) }
def core_formula_url
"#{core_tap.default_remote}/blob/HEAD/Formula/#{cask.token}.rb"
end end
end end
end end

View File

@ -144,6 +144,7 @@ module Cask
quarantine: @quarantine, quarantine: @quarantine,
) )
audit.run! audit.run!
audit
end end
def language_blocks def language_blocks

View File

@ -33,15 +33,13 @@ describe Cask::Audit, :cask do
let(:cask) { instance_double(Cask::Cask) } let(:cask) { instance_double(Cask::Cask) }
let(:new_cask) { nil } let(:new_cask) { nil }
let(:online) { nil } let(:online) { nil }
let(:only) { [] }
let(:strict) { nil } let(:strict) { nil }
let(:token_conflicts) { nil } let(:token_conflicts) { nil }
let(:audit) { let(:audit) {
described_class.new(cask, online: online, described_class.new(cask, online: online,
strict: strict, strict: strict,
new_cask: new_cask, new_cask: new_cask,
token_conflicts: token_conflicts, token_conflicts: token_conflicts)
only: only)
} }
describe "#new" do describe "#new" do
@ -134,7 +132,6 @@ describe Cask::Audit, :cask do
describe "token validation" do describe "token validation" do
let(:strict) { true } let(:strict) { true }
let(:only) { ["token_valid"] }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do cask '#{cask_token}' do
@ -231,7 +228,6 @@ describe Cask::Audit, :cask do
describe "token bad words" do describe "token bad words" do
let(:new_cask) { true } let(:new_cask) { true }
let(:only) { ["token_bad_words", "reverse_migration"] }
let(:online) { false } let(:online) { false }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
@ -347,7 +343,6 @@ describe Cask::Audit, :cask do
end end
describe "locale validation" do describe "locale validation" do
let(:only) { ["languages"] }
let(:cask) do let(:cask) do
tmp_cask "locale-cask-test", <<~RUBY tmp_cask "locale-cask-test", <<~RUBY
cask 'locale-cask-test' do cask 'locale-cask-test' do
@ -395,7 +390,6 @@ describe Cask::Audit, :cask do
end end
describe "pkg allow_untrusted checks" do describe "pkg allow_untrusted checks" do
let(:only) { ["untrusted_pkg"] }
let(:message) { "allow_untrusted is not permitted in official Homebrew Cask taps" } let(:message) { "allow_untrusted is not permitted in official Homebrew Cask taps" }
context "when the Cask has no pkg stanza" do context "when the Cask has no pkg stanza" do
@ -418,7 +412,6 @@ describe Cask::Audit, :cask do
end end
describe "signing checks" do describe "signing checks" do
let(:only) { ["signing"] }
let(:download_double) { instance_double(Cask::Download) } let(:download_double) { instance_double(Cask::Download) }
let(:unpack_double) { instance_double(UnpackStrategy::Zip) } let(:unpack_double) { instance_double(UnpackStrategy::Zip) }
@ -466,7 +459,6 @@ describe Cask::Audit, :cask do
end end
describe "livecheck should be skipped" do describe "livecheck should be skipped" do
let(:only) { ["livecheck_version"] }
let(:online) { true } let(:online) { true }
let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ } let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ }
@ -520,7 +512,6 @@ describe Cask::Audit, :cask do
end end
describe "when the Cask stanza requires uninstall" do describe "when the Cask stanza requires uninstall" do
let(:only) { ["stanza_requires_uninstall"] }
let(:message) { "installer and pkg stanzas require an uninstall stanza" } let(:message) { "installer and pkg stanzas require an uninstall stanza" }
context "when the Cask does not require an uninstall" do context "when the Cask does not require an uninstall" do
@ -690,14 +681,12 @@ describe Cask::Audit, :cask do
let(:message) { "you should use version :latest instead of version 'latest'" } let(:message) { "you should use version :latest instead of version 'latest'" }
context "when version is 'latest'" do context "when version is 'latest'" do
let(:only) { ["no_string_version_latest"] }
let(:cask_token) { "version-latest-string" } let(:cask_token) { "version-latest-string" }
it { is_expected.to fail_with(message) } it { is_expected.to fail_with(message) }
end end
context "when version is :latest" do context "when version is :latest" do
let(:only) { ["sha256_no_check_if_latest"] }
let(:cask_token) { "version-latest-with-checksum" } let(:cask_token) { "version-latest-with-checksum" }
it { is_expected.not_to fail_with(message) } it { is_expected.not_to fail_with(message) }
@ -706,21 +695,18 @@ describe Cask::Audit, :cask do
describe "sha256 checks" do describe "sha256 checks" do
context "when version is :latest and sha256 is not :no_check" do context "when version is :latest and sha256 is not :no_check" do
let(:only) { ["sha256_no_check_if_latest"] }
let(:cask_token) { "version-latest-with-checksum" } let(:cask_token) { "version-latest-with-checksum" }
it { is_expected.to fail_with("you should use sha256 :no_check when version is :latest") } it { is_expected.to fail_with("you should use sha256 :no_check when version is :latest") }
end end
context "when sha256 is not a legal SHA-256 digest" do context "when sha256 is not a legal SHA-256 digest" do
let(:only) { ["sha256_actually_256"] }
let(:cask_token) { "invalid-sha256" } let(:cask_token) { "invalid-sha256" }
it { is_expected.to fail_with("sha256 string must be of 64 hexadecimal characters") } it { is_expected.to fail_with("sha256 string must be of 64 hexadecimal characters") }
end end
context "when sha256 is sha256 for empty string" do context "when sha256 is sha256 for empty string" do
let(:only) { ["sha256_invalid"] }
let(:cask_token) { "sha256-for-empty-string" } let(:cask_token) { "sha256-for-empty-string" }
it { is_expected.to fail_with(/cannot use the sha256 for an empty string/) } it { is_expected.to fail_with(/cannot use the sha256 for an empty string/) }
@ -728,7 +714,6 @@ describe Cask::Audit, :cask do
end end
describe "hosting with livecheck checks" do describe "hosting with livecheck checks" do
let(:only) { ["hosting_with_livecheck"] }
let(:message) { /please add a livecheck/ } let(:message) { /please add a livecheck/ }
context "when the download does not use hosting with a livecheck" do context "when the download does not use hosting with a livecheck" do
@ -776,7 +761,6 @@ describe Cask::Audit, :cask do
end end
describe "latest with appcast checks" do describe "latest with appcast checks" do
let(:only) { ["latest_with_appcast_or_livecheck"] }
let(:message) { "Casks with an `appcast` should not use `version :latest`." } let(:message) { "Casks with an `appcast` should not use `version :latest`." }
context "when the Cask is :latest and does not have an appcast" do context "when the Cask is :latest and does not have an appcast" do
@ -799,8 +783,6 @@ describe Cask::Audit, :cask do
end end
describe "denylist checks" do describe "denylist checks" do
let(:only) { ["denylist"] }
context "when the Cask is not on the denylist" do context "when the Cask is not on the denylist" do
let(:cask_token) { "adobe-air" } let(:cask_token) { "adobe-air" }
@ -823,7 +805,6 @@ describe Cask::Audit, :cask do
end end
describe "latest with auto_updates checks" do describe "latest with auto_updates checks" do
let(:only) { ["latest_with_auto_updates"] }
let(:message) { "Casks with `version :latest` should not use `auto_updates`." } let(:message) { "Casks with `version :latest` should not use `auto_updates`." }
context "when the Cask is :latest and does not have auto_updates" do context "when the Cask is :latest and does not have auto_updates" do
@ -852,7 +833,6 @@ describe Cask::Audit, :cask do
end end
describe "preferred download URL formats" do describe "preferred download URL formats" do
let(:only) { ["download_url_format"] }
let(:message) { /URL format incorrect/ } let(:message) { /URL format incorrect/ }
context "with incorrect SourceForge URL format" do context "with incorrect SourceForge URL format" do
@ -887,8 +867,6 @@ describe Cask::Audit, :cask do
end end
describe "generic artifact checks" do describe "generic artifact checks" do
let(:only) { ["generic_artifacts"] }
context "with relative target" do context "with relative target" do
let(:cask_token) { "generic-artifact-relative-target" } let(:cask_token) { "generic-artifact-relative-target" }
@ -909,8 +887,6 @@ describe Cask::Audit, :cask do
end end
describe "url checks" do describe "url checks" do
let(:only) { %w[unnecessary_verified missing_verified no_match] }
context "with a block" do context "with a block" do
let(:cask_token) { "booby-trap" } let(:cask_token) { "booby-trap" }
@ -924,7 +900,7 @@ describe Cask::Audit, :cask do
let(:online) { false } let(:online) { false }
it "does not evaluate the block" do it "does not evaluate the block" do
expect(run).not_to fail_with(/Boom/) expect(run).not_to pass
end end
end end
@ -939,7 +915,6 @@ describe Cask::Audit, :cask do
end end
describe "token conflicts" do describe "token conflicts" do
let(:only) { ["token_conflicts"] }
let(:cask_token) { "with-binary" } let(:cask_token) { "with-binary" }
let(:token_conflicts) { true } let(:token_conflicts) { true }
@ -960,7 +935,6 @@ describe Cask::Audit, :cask do
end end
describe "audit of downloads" do describe "audit of downloads" do
let(:only) { ["download"] }
let(:cask_token) { "basic-cask" } let(:cask_token) { "basic-cask" }
let(:cask) { Cask::CaskLoader.load(cask_token) } let(:cask) { Cask::CaskLoader.load(cask_token) }
let(:download_double) { instance_double(Cask::Download) } let(:download_double) { instance_double(Cask::Download) }
@ -985,7 +959,6 @@ describe Cask::Audit, :cask do
context "when an exception is raised" do context "when an exception is raised" do
let(:cask) { instance_double(Cask::Cask) } let(:cask) { instance_double(Cask::Cask) }
let(:only) { ["description_present"] }
it "fails the audit" do it "fails the audit" do
expect(cask).to receive(:tap).and_raise(StandardError.new) expect(cask).to receive(:tap).and_raise(StandardError.new)
@ -993,8 +966,7 @@ describe Cask::Audit, :cask do
end end
end end
describe "checking description" do describe "without description" do
let(:only) { ["description_present"] }
let(:cask_token) { "without-description" } let(:cask_token) { "without-description" }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
@ -1024,6 +996,7 @@ describe Cask::Audit, :cask do
expect(run).to warn_with(/should have a description/) expect(run).to warn_with(/should have a description/)
end end
end end
end
context "with description" do context "with description" do
let(:cask_token) { "with-description" } let(:cask_token) { "with-description" }
@ -1045,13 +1018,9 @@ describe Cask::Audit, :cask do
expect(run).to pass expect(run).to pass
end end
end end
end
describe "checking verified" do
let(:only) { %w[unnecessary_verified missing_verified no_match required_stanzas] }
let(:cask_token) { "foo" }
context "when the url matches the homepage" do context "when the url matches the homepage" do
let(:cask_token) { "foo" }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do cask '#{cask_token}' do
@ -1070,6 +1039,7 @@ describe Cask::Audit, :cask do
end end
context "when the url does not match the homepage" do context "when the url does not match the homepage" do
let(:cask_token) { "foo" }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do cask '#{cask_token}' do
@ -1088,6 +1058,7 @@ describe Cask::Audit, :cask do
end end
context "when the url does not match the homepage with verified" do context "when the url does not match the homepage with verified" do
let(:cask_token) { "foo" }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
cask "#{cask_token}" do cask "#{cask_token}" do
@ -1106,6 +1077,7 @@ describe Cask::Audit, :cask do
end end
context "when there is no homepage" do context "when there is no homepage" do
let(:cask_token) { "foo" }
let(:cask) do let(:cask) do
tmp_cask cask_token.to_s, <<~RUBY tmp_cask cask_token.to_s, <<~RUBY
cask '#{cask_token}' do cask '#{cask_token}' do
@ -1153,4 +1125,3 @@ describe Cask::Audit, :cask do
end end
end end
end end
end