Merge pull request #13746 from SMillerDev/chore/cask/cleanup_audit
Cask: automatically add cask audits
This commit is contained in:
		
						commit
						13b060ab86
					
				@ -24,7 +24,7 @@ module Cask
 | 
			
		||||
 | 
			
		||||
    def initialize(cask, appcast: nil, download: nil, quarantine: nil,
 | 
			
		||||
                   token_conflicts: nil, online: nil, strict: nil, signing: nil,
 | 
			
		||||
                   new_cask: nil)
 | 
			
		||||
                   new_cask: nil, only: [], except: [])
 | 
			
		||||
 | 
			
		||||
      # `new_cask` implies `online`, `token_conflicts`, `strict` and `signing`
 | 
			
		||||
      online = new_cask if online.nil?
 | 
			
		||||
@ -47,44 +47,22 @@ module Cask
 | 
			
		||||
      @signing = signing
 | 
			
		||||
      @new_cask = new_cask
 | 
			
		||||
      @token_conflicts = token_conflicts
 | 
			
		||||
      @only = only
 | 
			
		||||
      @except = except
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def run!
 | 
			
		||||
      check_denylist
 | 
			
		||||
      check_reverse_migration
 | 
			
		||||
      check_required_stanzas
 | 
			
		||||
      check_version
 | 
			
		||||
      check_sha256
 | 
			
		||||
      check_desc
 | 
			
		||||
      check_url
 | 
			
		||||
      check_unnecessary_verified
 | 
			
		||||
      check_missing_verified
 | 
			
		||||
      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
 | 
			
		||||
      only_audits = @only
 | 
			
		||||
      except_audits = @except
 | 
			
		||||
 | 
			
		||||
      private_methods.map(&:to_s).grep(/^check_/).each do |audit_method_name|
 | 
			
		||||
        name = audit_method_name.delete_prefix("check_")
 | 
			
		||||
        next if !only_audits.empty? && only_audits&.exclude?(name)
 | 
			
		||||
        next if except_audits&.include?(name)
 | 
			
		||||
 | 
			
		||||
        send(audit_method_name)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      self
 | 
			
		||||
    rescue => e
 | 
			
		||||
      odebug e, e.backtrace
 | 
			
		||||
@ -100,10 +78,27 @@ module Cask
 | 
			
		||||
      @warnings ||= []
 | 
			
		||||
    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)
 | 
			
		||||
      errors << ({ message: message, location: location })
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { params(message: String, location: T.nilable(String)).void }
 | 
			
		||||
    def add_warning(message, location: nil)
 | 
			
		||||
      if strict?
 | 
			
		||||
        add_error message, location: location
 | 
			
		||||
@ -112,14 +107,6 @@ module Cask
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def errors?
 | 
			
		||||
      errors.any?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def warnings?
 | 
			
		||||
      warnings.any?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def result
 | 
			
		||||
      if errors?
 | 
			
		||||
        Formatter.error("failed")
 | 
			
		||||
@ -150,12 +137,9 @@ module Cask
 | 
			
		||||
      summary.join("\n")
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def success?
 | 
			
		||||
      !(errors? || warnings?)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_untrusted_pkg
 | 
			
		||||
      odebug "Auditing pkg stanza: allow_untrusted"
 | 
			
		||||
 | 
			
		||||
@ -170,6 +154,7 @@ module Cask
 | 
			
		||||
      add_error "allow_untrusted is not permitted in official Homebrew Cask taps"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_stanza_requires_uninstall
 | 
			
		||||
      odebug "Auditing stanzas which require an uninstall"
 | 
			
		||||
 | 
			
		||||
@ -179,6 +164,7 @@ module Cask
 | 
			
		||||
      add_error "installer and pkg stanzas require an uninstall stanza"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_single_pre_postflight
 | 
			
		||||
      odebug "Auditing preflight and postflight stanzas"
 | 
			
		||||
 | 
			
		||||
@ -195,6 +181,7 @@ module Cask
 | 
			
		||||
      add_error "only a single postflight stanza is allowed"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_single_uninstall_zap
 | 
			
		||||
      odebug "Auditing single uninstall_* and zap stanzas"
 | 
			
		||||
 | 
			
		||||
@ -221,6 +208,7 @@ module Cask
 | 
			
		||||
      add_error "only a single zap stanza is allowed"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_required_stanzas
 | 
			
		||||
      odebug "Auditing required stanzas"
 | 
			
		||||
      [:version, :sha256, :url, :homepage].each do |sym|
 | 
			
		||||
@ -233,58 +221,66 @@ module Cask
 | 
			
		||||
      add_error "at least one activatable artifact stanza is required" if installable_artifacts.empty?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def check_version
 | 
			
		||||
      return unless cask.version
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_description_present
 | 
			
		||||
      # Fonts seldom benefit from descriptions and requiring them disproportionately increases the maintenance burden
 | 
			
		||||
      return if cask.tap == "homebrew/cask-fonts"
 | 
			
		||||
 | 
			
		||||
      check_no_string_version_latest
 | 
			
		||||
      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
 | 
			
		||||
      odebug "Verifying version :latest does not appear as a string ('latest')"
 | 
			
		||||
      return unless cask.version
 | 
			
		||||
 | 
			
		||||
      odebug "Auditing version :latest does not appear as a string ('latest')"
 | 
			
		||||
      return unless cask.version.raw_version == "latest"
 | 
			
		||||
 | 
			
		||||
      add_error "you should use version :latest instead of version 'latest'"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def check_sha256
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_sha256_no_check_if_latest
 | 
			
		||||
      return unless cask.sha256
 | 
			
		||||
 | 
			
		||||
      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"
 | 
			
		||||
      odebug "Auditing sha256 :no_check with version :latest"
 | 
			
		||||
      return unless cask.version.latest?
 | 
			
		||||
      return if cask.sha256 == :no_check
 | 
			
		||||
 | 
			
		||||
      add_error "you should use sha256 :no_check when version is :latest"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_sha256_no_check_if_unversioned
 | 
			
		||||
      return unless cask.sha256
 | 
			
		||||
      return if cask.sha256 == :no_check
 | 
			
		||||
 | 
			
		||||
      add_error "Use `sha256 :no_check` when URL is unversioned." if cask.url&.unversioned?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_sha256_actually_256
 | 
			
		||||
      odebug "Verifying sha256 string is a legal SHA-256 digest"
 | 
			
		||||
      return unless cask.sha256
 | 
			
		||||
 | 
			
		||||
      odebug "Auditing sha256 string is a legal SHA-256 digest"
 | 
			
		||||
      return unless cask.sha256.is_a?(Checksum)
 | 
			
		||||
      return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i]
 | 
			
		||||
 | 
			
		||||
      add_error "sha256 string must be of 64 hexadecimal characters"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_sha256_invalid
 | 
			
		||||
      odebug "Verifying sha256 is not a known invalid value"
 | 
			
		||||
      return unless cask.sha256
 | 
			
		||||
 | 
			
		||||
      odebug "Auditing sha256 is not a known invalid value"
 | 
			
		||||
      empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
 | 
			
		||||
      return unless cask.sha256 == empty_sha256
 | 
			
		||||
 | 
			
		||||
      add_error "cannot use the sha256 for an empty string: #{empty_sha256}"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_appcast_and_livecheck
 | 
			
		||||
      return unless cask.appcast
 | 
			
		||||
 | 
			
		||||
@ -295,6 +291,7 @@ module Cask
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_latest_with_appcast_or_livecheck
 | 
			
		||||
      return unless cask.version.latest?
 | 
			
		||||
 | 
			
		||||
@ -302,6 +299,7 @@ module Cask
 | 
			
		||||
      add_error "Casks with a `livecheck` should not use `version :latest`." if cask.livecheckable?
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_latest_with_auto_updates
 | 
			
		||||
      return unless cask.version.latest?
 | 
			
		||||
      return unless cask.auto_updates
 | 
			
		||||
@ -311,7 +309,8 @@ module Cask
 | 
			
		||||
 | 
			
		||||
    LIVECHECK_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#stanza-livecheck"
 | 
			
		||||
 | 
			
		||||
    def check_hosting_with_livecheck(livecheck_result:)
 | 
			
		||||
    sig { params(livecheck_result: T::Boolean).void }
 | 
			
		||||
    def check_hosting_with_livecheck(livecheck_result: check_livecheck_version)
 | 
			
		||||
      return if cask.discontinued? || cask.version.latest?
 | 
			
		||||
      return if block_url_offline? || cask.appcast || cask.livecheckable?
 | 
			
		||||
      return if livecheck_result == :auto_detected
 | 
			
		||||
@ -330,24 +329,12 @@ module Cask
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def check_desc
 | 
			
		||||
      return if cask.desc.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."
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def check_url
 | 
			
		||||
      return unless cask.url
 | 
			
		||||
 | 
			
		||||
      check_download_url_format
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    SOURCEFORGE_OSDN_REFERENCE_URL = "https://docs.brew.sh/Cask-Cookbook#sourceforgeosdn-urls"
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_download_url_format
 | 
			
		||||
      return unless cask.url
 | 
			
		||||
 | 
			
		||||
      odebug "Auditing URL format"
 | 
			
		||||
      if bad_sourceforge_url?
 | 
			
		||||
        add_error "SourceForge URL format incorrect. See #{Formatter.url(SOURCEFORGE_OSDN_REFERENCE_URL)}"
 | 
			
		||||
@ -356,82 +343,9 @@ module Cask
 | 
			
		||||
      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"
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_unnecessary_verified
 | 
			
		||||
      return if block_url_offline?
 | 
			
		||||
      return unless verified_present?
 | 
			
		||||
@ -443,6 +357,7 @@ module Cask
 | 
			
		||||
                "See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_missing_verified
 | 
			
		||||
      return if block_url_offline?
 | 
			
		||||
      return if file_url?
 | 
			
		||||
@ -454,6 +369,7 @@ module Cask
 | 
			
		||||
                "See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_no_match
 | 
			
		||||
      return if block_url_offline?
 | 
			
		||||
      return unless verified_present?
 | 
			
		||||
@ -464,6 +380,7 @@ module Cask
 | 
			
		||||
                "See #{Formatter.url(VERIFIED_URL_REFERENCE_URL)}"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_generic_artifacts
 | 
			
		||||
      cask.artifacts.select { |a| a.is_a?(Artifact::Artifact) }.each do |artifact|
 | 
			
		||||
        unless artifact.target.absolute?
 | 
			
		||||
@ -472,6 +389,7 @@ module Cask
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_languages
 | 
			
		||||
      @cask.languages.each do |language|
 | 
			
		||||
        Locale.parse(language)
 | 
			
		||||
@ -480,6 +398,7 @@ module Cask
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_token_conflicts
 | 
			
		||||
      return unless token_conflicts?
 | 
			
		||||
      return unless core_formula_names.include?(cask.token)
 | 
			
		||||
@ -488,6 +407,7 @@ module Cask
 | 
			
		||||
                  "#{Formatter.url(core_formula_url)}"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_token_valid
 | 
			
		||||
      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? "+"
 | 
			
		||||
@ -505,6 +425,7 @@ module Cask
 | 
			
		||||
      add_error "cask token should not have leading or trailing hyphens"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_token_bad_words
 | 
			
		||||
      return unless new_cask?
 | 
			
		||||
 | 
			
		||||
@ -532,19 +453,7 @@ module Cask
 | 
			
		||||
      add_warning "cask token mentions framework"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_download
 | 
			
		||||
      return if download.blank? || cask.url.blank?
 | 
			
		||||
 | 
			
		||||
@ -554,11 +463,11 @@ module Cask
 | 
			
		||||
      add_error "download not possible: #{e}"
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_signing
 | 
			
		||||
      return if !signing? || download.blank? || cask.url.blank?
 | 
			
		||||
 | 
			
		||||
      odebug "Auditing signing"
 | 
			
		||||
      odebug cask.artifacts
 | 
			
		||||
      artifacts = cask.artifacts.select { |k| k.is_a?(Artifact::Pkg) || k.is_a?(Artifact::App) }
 | 
			
		||||
 | 
			
		||||
      return if artifacts.empty?
 | 
			
		||||
@ -598,6 +507,7 @@ module Cask
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { returns(T.nilable(T.any(T::Boolean, Symbol))) }
 | 
			
		||||
    def check_livecheck_version
 | 
			
		||||
      return unless appcast?
 | 
			
		||||
 | 
			
		||||
@ -635,6 +545,7 @@ module Cask
 | 
			
		||||
      false
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_appcast_contains_version
 | 
			
		||||
      return unless appcast?
 | 
			
		||||
      return if cask.appcast.to_s.empty?
 | 
			
		||||
@ -661,6 +572,7 @@ module Cask
 | 
			
		||||
      EOS
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_github_prerelease_version
 | 
			
		||||
      return if cask.tap == "homebrew/cask-versions"
 | 
			
		||||
 | 
			
		||||
@ -674,6 +586,7 @@ module Cask
 | 
			
		||||
      add_error error if error
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_gitlab_prerelease_version
 | 
			
		||||
      return if cask.tap == "homebrew/cask-versions"
 | 
			
		||||
 | 
			
		||||
@ -688,6 +601,7 @@ module Cask
 | 
			
		||||
      add_error error if error
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_github_repository_archived
 | 
			
		||||
      user, repo = get_repo_data(%r{https?://github\.com/([^/]+)/([^/]+)/?.*}) if online?
 | 
			
		||||
      return if user.nil?
 | 
			
		||||
@ -708,6 +622,7 @@ module Cask
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_gitlab_repository_archived
 | 
			
		||||
      user, repo = get_repo_data(%r{https?://gitlab\.com/([^/]+)/([^/]+)/?.*}) if online?
 | 
			
		||||
      return if user.nil?
 | 
			
		||||
@ -728,6 +643,7 @@ module Cask
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_github_repository
 | 
			
		||||
      return unless new_cask?
 | 
			
		||||
 | 
			
		||||
@ -740,6 +656,7 @@ module Cask
 | 
			
		||||
      add_error error if error
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_gitlab_repository
 | 
			
		||||
      return unless new_cask?
 | 
			
		||||
 | 
			
		||||
@ -752,6 +669,7 @@ module Cask
 | 
			
		||||
      add_error error if error
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    sig { void }
 | 
			
		||||
    def check_bitbucket_repository
 | 
			
		||||
      return unless new_cask?
 | 
			
		||||
 | 
			
		||||
@ -764,6 +682,62 @@ module Cask
 | 
			
		||||
      add_error error if error
 | 
			
		||||
    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)
 | 
			
		||||
      return unless online?
 | 
			
		||||
 | 
			
		||||
@ -777,52 +751,105 @@ module Cask
 | 
			
		||||
      [user, repo]
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def check_denylist
 | 
			
		||||
      return unless cask.tap
 | 
			
		||||
      return unless cask.tap.official?
 | 
			
		||||
      return unless (reason = Denylist.reason(cask.token))
 | 
			
		||||
    sig { params(regex: T.any(String, Regexp), valid_formats_array: T::Array[String]).returns(T::Boolean) }
 | 
			
		||||
    def bad_url_format?(regex, valid_formats_array)
 | 
			
		||||
      return false unless cask.url.to_s.match?(regex)
 | 
			
		||||
 | 
			
		||||
      add_error "#{cask.token} is not allowed: #{reason}"
 | 
			
		||||
      valid_formats_array.none? { |format| cask.url.to_s =~ format }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    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"
 | 
			
		||||
    sig { returns(T::Boolean) }
 | 
			
		||||
    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 check_https_availability
 | 
			
		||||
      return unless download
 | 
			
		||||
 | 
			
		||||
      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])
 | 
			
		||||
    sig { returns(T::Boolean) }
 | 
			
		||||
    def bad_osdn_url?
 | 
			
		||||
      bad_url_format?(/osd/, [%r{\Ahttps?://([^/]+.)?dl\.osdn\.jp/}])
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
      if cask.appcast && appcast?
 | 
			
		||||
        check_url_for_https_availability(cask.appcast, "appcast URL", cask.token, cask.tap, check_content: true)
 | 
			
		||||
    sig { returns(String) }
 | 
			
		||||
    def homepage
 | 
			
		||||
      URI(cask.homepage.to_s).host
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
      return unless cask.homepage
 | 
			
		||||
 | 
			
		||||
      check_url_for_https_availability(cask.homepage, "homepage URL", cask.token, cask.tap,
 | 
			
		||||
                                       user_agents:   [:browser, :default],
 | 
			
		||||
                                       check_content: true,
 | 
			
		||||
                                       strict:        strict?)
 | 
			
		||||
    sig { returns(String) }
 | 
			
		||||
    def domain
 | 
			
		||||
      URI(cask.url.to_s).host
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def check_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)
 | 
			
		||||
    sig { returns(T::Boolean) }
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
      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
 | 
			
		||||
    sig { params(url: String).returns(String) }
 | 
			
		||||
    def strip_url_scheme(url)
 | 
			
		||||
      url.sub(%r{^[^:/]+://(www\.)?}, "")
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
@ -144,7 +144,6 @@ module Cask
 | 
			
		||||
        quarantine:      @quarantine,
 | 
			
		||||
      )
 | 
			
		||||
      audit.run!
 | 
			
		||||
      audit
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def language_blocks
 | 
			
		||||
 | 
			
		||||
@ -33,13 +33,15 @@ describe Cask::Audit, :cask do
 | 
			
		||||
  let(:cask) { instance_double(Cask::Cask) }
 | 
			
		||||
  let(:new_cask) { nil }
 | 
			
		||||
  let(:online) { nil }
 | 
			
		||||
  let(:only) { [] }
 | 
			
		||||
  let(:strict) { nil }
 | 
			
		||||
  let(:token_conflicts) { nil }
 | 
			
		||||
  let(:audit) {
 | 
			
		||||
    described_class.new(cask, online:          online,
 | 
			
		||||
                              strict:          strict,
 | 
			
		||||
                              new_cask:        new_cask,
 | 
			
		||||
                              token_conflicts: token_conflicts)
 | 
			
		||||
                              token_conflicts: token_conflicts,
 | 
			
		||||
                              only:            only)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  describe "#new" do
 | 
			
		||||
@ -132,6 +134,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
 | 
			
		||||
    describe "token validation" do
 | 
			
		||||
      let(:strict) { true }
 | 
			
		||||
      let(:only) { ["token_valid"] }
 | 
			
		||||
      let(:cask) do
 | 
			
		||||
        tmp_cask cask_token.to_s, <<~RUBY
 | 
			
		||||
          cask '#{cask_token}' do
 | 
			
		||||
@ -228,6 +231,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
 | 
			
		||||
    describe "token bad words" do
 | 
			
		||||
      let(:new_cask) { true }
 | 
			
		||||
      let(:only) { ["token_bad_words", "reverse_migration"] }
 | 
			
		||||
      let(:online) { false }
 | 
			
		||||
      let(:cask) do
 | 
			
		||||
        tmp_cask cask_token.to_s, <<~RUBY
 | 
			
		||||
@ -343,6 +347,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "locale validation" do
 | 
			
		||||
      let(:only) { ["languages"] }
 | 
			
		||||
      let(:cask) do
 | 
			
		||||
        tmp_cask "locale-cask-test", <<~RUBY
 | 
			
		||||
          cask 'locale-cask-test' do
 | 
			
		||||
@ -390,6 +395,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "pkg allow_untrusted checks" do
 | 
			
		||||
      let(:only) { ["untrusted_pkg"] }
 | 
			
		||||
      let(:message) { "allow_untrusted is not permitted in official Homebrew Cask taps" }
 | 
			
		||||
 | 
			
		||||
      context "when the Cask has no pkg stanza" do
 | 
			
		||||
@ -412,6 +418,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "signing checks" do
 | 
			
		||||
      let(:only) { ["signing"] }
 | 
			
		||||
      let(:download_double) { instance_double(Cask::Download) }
 | 
			
		||||
      let(:unpack_double) { instance_double(UnpackStrategy::Zip) }
 | 
			
		||||
 | 
			
		||||
@ -459,6 +466,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "livecheck should be skipped" do
 | 
			
		||||
      let(:only) { ["livecheck_version"] }
 | 
			
		||||
      let(:online) { true }
 | 
			
		||||
      let(:message) { /Version '[^']*' differs from '[^']*' retrieved by livecheck\./ }
 | 
			
		||||
 | 
			
		||||
@ -512,6 +520,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "when the Cask stanza requires uninstall" do
 | 
			
		||||
      let(:only) { ["stanza_requires_uninstall"] }
 | 
			
		||||
      let(:message) { "installer and pkg stanzas require an uninstall stanza" }
 | 
			
		||||
 | 
			
		||||
      context "when the Cask does not require an uninstall" do
 | 
			
		||||
@ -681,12 +690,14 @@ describe Cask::Audit, :cask do
 | 
			
		||||
      let(:message) { "you should use version :latest instead of version 'latest'" }
 | 
			
		||||
 | 
			
		||||
      context "when version is 'latest'" do
 | 
			
		||||
        let(:only) { ["no_string_version_latest"] }
 | 
			
		||||
        let(:cask_token) { "version-latest-string" }
 | 
			
		||||
 | 
			
		||||
        it { is_expected.to fail_with(message) }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when version is :latest" do
 | 
			
		||||
        let(:only) { ["sha256_no_check_if_latest"] }
 | 
			
		||||
        let(:cask_token) { "version-latest-with-checksum" }
 | 
			
		||||
 | 
			
		||||
        it { is_expected.not_to fail_with(message) }
 | 
			
		||||
@ -695,18 +706,21 @@ describe Cask::Audit, :cask do
 | 
			
		||||
 | 
			
		||||
    describe "sha256 checks" 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" }
 | 
			
		||||
 | 
			
		||||
        it { is_expected.to fail_with("you should use sha256 :no_check when version is :latest") }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when sha256 is not a legal SHA-256 digest" do
 | 
			
		||||
        let(:only) { ["sha256_actually_256"] }
 | 
			
		||||
        let(:cask_token) { "invalid-sha256" }
 | 
			
		||||
 | 
			
		||||
        it { is_expected.to fail_with("sha256 string must be of 64 hexadecimal characters") }
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when sha256 is sha256 for empty string" do
 | 
			
		||||
        let(:only) { ["sha256_invalid"] }
 | 
			
		||||
        let(:cask_token) { "sha256-for-empty-string" }
 | 
			
		||||
 | 
			
		||||
        it { is_expected.to fail_with(/cannot use the sha256 for an empty string/) }
 | 
			
		||||
@ -714,6 +728,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "hosting with livecheck checks" do
 | 
			
		||||
      let(:only) { ["hosting_with_livecheck"] }
 | 
			
		||||
      let(:message) { /please add a livecheck/ }
 | 
			
		||||
 | 
			
		||||
      context "when the download does not use hosting with a livecheck" do
 | 
			
		||||
@ -761,6 +776,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "latest with appcast checks" do
 | 
			
		||||
      let(:only) { ["latest_with_appcast_or_livecheck"] }
 | 
			
		||||
      let(:message) { "Casks with an `appcast` should not use `version :latest`." }
 | 
			
		||||
 | 
			
		||||
      context "when the Cask is :latest and does not have an appcast" do
 | 
			
		||||
@ -783,6 +799,8 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "denylist checks" do
 | 
			
		||||
      let(:only) { ["denylist"] }
 | 
			
		||||
 | 
			
		||||
      context "when the Cask is not on the denylist" do
 | 
			
		||||
        let(:cask_token) { "adobe-air" }
 | 
			
		||||
 | 
			
		||||
@ -805,6 +823,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "latest with auto_updates checks" do
 | 
			
		||||
      let(:only) { ["latest_with_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
 | 
			
		||||
@ -833,6 +852,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "preferred download URL formats" do
 | 
			
		||||
      let(:only) { ["download_url_format"] }
 | 
			
		||||
      let(:message) { /URL format incorrect/ }
 | 
			
		||||
 | 
			
		||||
      context "with incorrect SourceForge URL format" do
 | 
			
		||||
@ -867,6 +887,8 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "generic artifact checks" do
 | 
			
		||||
      let(:only) { ["generic_artifacts"] }
 | 
			
		||||
 | 
			
		||||
      context "with relative target" do
 | 
			
		||||
        let(:cask_token) { "generic-artifact-relative-target" }
 | 
			
		||||
 | 
			
		||||
@ -887,6 +909,8 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "url checks" do
 | 
			
		||||
      let(:only) { %w[unnecessary_verified missing_verified no_match] }
 | 
			
		||||
 | 
			
		||||
      context "with a block" do
 | 
			
		||||
        let(:cask_token) { "booby-trap" }
 | 
			
		||||
 | 
			
		||||
@ -900,7 +924,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
          let(:online) { false }
 | 
			
		||||
 | 
			
		||||
          it "does not evaluate the block" do
 | 
			
		||||
            expect(run).not_to pass
 | 
			
		||||
            expect(run).not_to fail_with(/Boom/)
 | 
			
		||||
          end
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
@ -915,6 +939,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "token conflicts" do
 | 
			
		||||
      let(:only) { ["token_conflicts"] }
 | 
			
		||||
      let(:cask_token) { "with-binary" }
 | 
			
		||||
      let(:token_conflicts) { true }
 | 
			
		||||
 | 
			
		||||
@ -935,6 +960,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "audit of downloads" do
 | 
			
		||||
      let(:only) { ["download"] }
 | 
			
		||||
      let(:cask_token) { "basic-cask" }
 | 
			
		||||
      let(:cask) { Cask::CaskLoader.load(cask_token) }
 | 
			
		||||
      let(:download_double) { instance_double(Cask::Download) }
 | 
			
		||||
@ -959,6 +985,7 @@ describe Cask::Audit, :cask do
 | 
			
		||||
 | 
			
		||||
    context "when an exception is raised" do
 | 
			
		||||
      let(:cask) { instance_double(Cask::Cask) }
 | 
			
		||||
      let(:only) { ["description_present"] }
 | 
			
		||||
 | 
			
		||||
      it "fails the audit" do
 | 
			
		||||
        expect(cask).to receive(:tap).and_raise(StandardError.new)
 | 
			
		||||
@ -966,7 +993,8 @@ describe Cask::Audit, :cask do
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    describe "without description" do
 | 
			
		||||
    describe "checking description" do
 | 
			
		||||
      let(:only) { ["description_present"] }
 | 
			
		||||
      let(:cask_token) { "without-description" }
 | 
			
		||||
      let(:cask) do
 | 
			
		||||
        tmp_cask cask_token.to_s, <<~RUBY
 | 
			
		||||
@ -996,7 +1024,6 @@ describe Cask::Audit, :cask do
 | 
			
		||||
          expect(run).to warn_with(/should have a description/)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
      context "with description" do
 | 
			
		||||
        let(:cask_token) { "with-description" }
 | 
			
		||||
@ -1018,9 +1045,13 @@ describe Cask::Audit, :cask do
 | 
			
		||||
          expect(run).to pass
 | 
			
		||||
        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
 | 
			
		||||
      let(:cask_token) { "foo" }
 | 
			
		||||
        let(:cask) do
 | 
			
		||||
          tmp_cask cask_token.to_s, <<~RUBY
 | 
			
		||||
            cask '#{cask_token}' do
 | 
			
		||||
@ -1039,7 +1070,6 @@ describe Cask::Audit, :cask do
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when the url does not match the homepage" do
 | 
			
		||||
      let(:cask_token) { "foo" }
 | 
			
		||||
        let(:cask) do
 | 
			
		||||
          tmp_cask cask_token.to_s, <<~RUBY
 | 
			
		||||
            cask '#{cask_token}' do
 | 
			
		||||
@ -1058,7 +1088,6 @@ describe Cask::Audit, :cask do
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when the url does not match the homepage with verified" do
 | 
			
		||||
      let(:cask_token) { "foo" }
 | 
			
		||||
        let(:cask) do
 | 
			
		||||
          tmp_cask cask_token.to_s, <<~RUBY
 | 
			
		||||
            cask "#{cask_token}" do
 | 
			
		||||
@ -1077,7 +1106,6 @@ describe Cask::Audit, :cask do
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context "when there is no homepage" do
 | 
			
		||||
      let(:cask_token) { "foo" }
 | 
			
		||||
        let(:cask) do
 | 
			
		||||
          tmp_cask cask_token.to_s, <<~RUBY
 | 
			
		||||
            cask '#{cask_token}' do
 | 
			
		||||
@ -1125,3 +1153,4 @@ describe Cask::Audit, :cask do
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user