brew/Library/Homebrew/cask/exceptions.rb
Issy Long 45978435e7
rubocop: Use Sorbet/StrictSigil as it's better than comments
- Previously I thought that comments were fine to discourage people from
  wasting their time trying to bump things that used `undef` that Sorbet
  didn't support. But RuboCop is better at this since it'll complain if
  the comments are unnecessary.

- Suggested in https://github.com/Homebrew/brew/pull/18018#issuecomment-2283369501.

- I've gone for a mixture of `rubocop:disable` for the files that can't
  be `typed: strict` (use of undef, required before everything else, etc)
  and `rubocop:todo` for everything else that should be tried to make
  strictly typed. There's no functional difference between the two as
  `rubocop:todo` is `rubocop:disable` with a different name.

- And I entirely disabled the cop for the docs/ directory since
  `typed: strict` isn't going to gain us anything for some Markdown
  linting config files.

- This means that now it's easier to track what needs to be done rather
  than relying on checklists of files in our big Sorbet issue:

```shell
$ git grep 'typed: true # rubocop:todo Sorbet/StrictSigil' | wc -l
    268
```

- And this is confirmed working for new files:

```shell
$ git status
On branch use-rubocop-for-sorbet-strict-sigils
Untracked files:
  (use "git add <file>..." to include in what will be committed)
        Library/Homebrew/bad.rb
        Library/Homebrew/good.rb

nothing added to commit but untracked files present (use "git add" to track)

$ brew style
Offenses:

bad.rb:1:1: C: Sorbet/StrictSigil: Sorbet sigil should be at least strict got true.
^^^^^^^^^^^^^

1340 files inspected, 1 offense detected
```
2024-08-12 15:24:27 +01:00

240 lines
5.9 KiB
Ruby

# typed: true # rubocop:todo Sorbet/StrictSigil
# frozen_string_literal: true
module Cask
# General cask error.
class CaskError < RuntimeError; end
# Cask error containing multiple other errors.
class MultipleCaskErrors < CaskError
def initialize(errors)
super()
@errors = errors
end
sig { returns(String) }
def to_s
<<~EOS
Problems with multiple casks:
#{@errors.map(&:to_s).join("\n")}
EOS
end
end
# Abstract cask error containing a cask token.
class AbstractCaskErrorWithToken < CaskError
sig { returns(String) }
attr_reader :token
sig { returns(String) }
attr_reader :reason
def initialize(token, reason = nil)
super()
@token = token.to_s
@reason = reason.to_s
end
end
# Error when a cask is not installed.
class CaskNotInstalledError < AbstractCaskErrorWithToken
sig { returns(String) }
def to_s
"Cask '#{token}' is not installed."
end
end
# Error when a cask cannot be installed.
class CaskCannotBeInstalledError < AbstractCaskErrorWithToken
attr_reader :message
def initialize(token, message)
super(token)
@message = message
end
sig { returns(String) }
def to_s
"Cask '#{token}' has been #{message}"
end
end
# Error when a cask conflicts with another cask.
class CaskConflictError < AbstractCaskErrorWithToken
attr_reader :conflicting_cask
def initialize(token, conflicting_cask)
super(token)
@conflicting_cask = conflicting_cask
end
sig { returns(String) }
def to_s
"Cask '#{token}' conflicts with '#{conflicting_cask}'."
end
end
# Error when a cask is not available.
class CaskUnavailableError < AbstractCaskErrorWithToken
sig { returns(String) }
def to_s
"Cask '#{token}' is unavailable#{reason.empty? ? "." : ": #{reason}"}"
end
end
# Error when a cask is unreadable.
class CaskUnreadableError < CaskUnavailableError
sig { returns(String) }
def to_s
"Cask '#{token}' is unreadable#{reason.empty? ? "." : ": #{reason}"}"
end
end
# Error when a cask in a specific tap is not available.
class TapCaskUnavailableError < CaskUnavailableError
attr_reader :tap
def initialize(tap, token)
super("#{tap}/#{token}")
@tap = tap
end
sig { returns(String) }
def to_s
s = super
s += "\nPlease tap it and then try again: brew tap #{tap}" unless tap.installed?
s
end
end
# Error when a cask with the same name is found in multiple taps.
class TapCaskAmbiguityError < CaskError
sig { returns(String) }
attr_reader :token
sig { returns(T::Array[CaskLoader::FromNameLoader]) }
attr_reader :loaders
sig { params(token: String, loaders: T::Array[CaskLoader::FromNameLoader]).void }
def initialize(token, loaders)
@loaders = loaders
taps = loaders.map(&:tap)
casks = taps.map { |tap| "#{tap}/#{token}" }
cask_list = casks.sort.map { |f| "\n * #{f}" }.join
super <<~EOS
Cask #{token} exists in multiple taps:#{cask_list}
Please use the fully-qualified name (e.g. #{casks.first}) to refer to a specific Cask.
EOS
end
end
# Error when a cask already exists.
class CaskAlreadyCreatedError < AbstractCaskErrorWithToken
sig { returns(String) }
def to_s
%Q(Cask '#{token}' already exists. Run #{Formatter.identifier("brew edit --cask #{token}")} to edit it.)
end
end
# Error when there is a cyclic cask dependency.
class CaskCyclicDependencyError < AbstractCaskErrorWithToken
sig { returns(String) }
def to_s
"Cask '#{token}' includes cyclic dependencies on other Casks#{reason.empty? ? "." : ": #{reason}"}"
end
end
# Error when a cask depends on itself.
class CaskSelfReferencingDependencyError < CaskCyclicDependencyError
sig { returns(String) }
def to_s
"Cask '#{token}' depends on itself."
end
end
# Error when no cask is specified.
class CaskUnspecifiedError < CaskError
sig { returns(String) }
def to_s
"This command requires a Cask token."
end
end
# Error when a cask is invalid.
class CaskInvalidError < AbstractCaskErrorWithToken
sig { returns(String) }
def to_s
"Cask '#{token}' definition is invalid#{reason.empty? ? "." : ": #{reason}"}"
end
end
# Error when a cask token does not match the file name.
class CaskTokenMismatchError < CaskInvalidError
def initialize(token, header_token)
super(token, "Token '#{header_token}' in header line does not match the file name.")
end
end
# Error during quarantining of a file.
class CaskQuarantineError < CaskError
attr_reader :path, :reason
def initialize(path, reason)
super()
@path = path
@reason = reason
end
sig { returns(String) }
def to_s
s = +"Failed to quarantine #{path}."
unless reason.empty?
s << " Here's the reason:\n"
s << Formatter.error(reason)
s << "\n" unless reason.end_with?("\n")
end
s.freeze
end
end
# Error while propagating quarantine information to subdirectories.
class CaskQuarantinePropagationError < CaskQuarantineError
sig { returns(String) }
def to_s
s = +"Failed to quarantine one or more files within #{path}."
unless reason.empty?
s << " Here's the reason:\n"
s << Formatter.error(reason)
s << "\n" unless reason.end_with?("\n")
end
s.freeze
end
end
# Error while removing quarantine information.
class CaskQuarantineReleaseError < CaskQuarantineError
sig { returns(String) }
def to_s
s = +"Failed to release #{path} from quarantine."
unless reason.empty?
s << " Here's the reason:\n"
s << Formatter.error(reason)
s << "\n" unless reason.end_with?("\n")
end
s.freeze
end
end
end