Merge pull request #15223 from issyl0/rubocop-cask-stanza-order-in-on-blocks

rubocops/cask: Check for correct stanza order within `on_*` blocks
This commit is contained in:
Markus Reiter 2023-05-08 09:36:30 +02:00 committed by GitHub
commit c0f8068573
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 288 additions and 258 deletions

View File

@ -6,27 +6,57 @@ require "forwardable"
module RuboCop
module Cask
module AST
# This class wraps the AST block node that represents the entire cask
# definition. It includes various helper methods to aid cops in their
# analysis.
class CaskBlock
extend Forwardable
class StanzaBlock
extend T::Helpers
sig { returns(RuboCop::AST::BlockNode) }
attr_reader :block_node
sig { returns(T::Array[Parser::Source::Comment]) }
attr_reader :comments
sig { params(block_node: RuboCop::AST::BlockNode, comments: T::Array[Parser::Source::Comment]).void }
def initialize(block_node, comments)
@block_node = block_node
@comments = comments
end
attr_reader :block_node, :comments
sig { returns(T::Array[Stanza]) }
def stanzas
return [] unless (block_body = block_node.block_body)
alias cask_node block_node
# If a block only contains one stanza, it is that stanza's direct parent, otherwise
# stanzas are grouped in a nested block and the block is that nested block's parent.
is_stanza = if block_body.begin_block?
->(node) { node.parent.parent == block_node }
else
->(node) { node.parent == block_node }
end
@stanzas ||= block_body.each_node
.select(&:stanza?)
.select(&is_stanza)
.map { |node| Stanza.new(node, comments) }
end
end
# This class wraps the AST block node that represents the entire cask
# definition. It includes various helper methods to aid cops in their
# analysis.
class CaskBlock < StanzaBlock
extend Forwardable
def cask_node
block_node
end
def_delegator :cask_node, :block_body, :cask_body
def header
@header ||= CaskHeader.new(cask_node.method_node)
@header ||= CaskHeader.new(block_node.method_node)
end
# TODO: Use `StanzaBlock#stanzas` for all cops, where possible.
def stanzas
return [] unless cask_body
@ -46,28 +76,6 @@ module RuboCop
@toplevel_stanzas ||= stanzas.select(&is_toplevel_stanza)
end
def sorted_toplevel_stanzas
@sorted_toplevel_stanzas ||= sort_stanzas(toplevel_stanzas)
end
private
def sort_stanzas(stanzas)
stanzas.sort do |s1, s2|
i1 = stanza_order_index(s1)
i2 = stanza_order_index(s2)
if i1 == i2 || i1.blank? || i2.blank?
i1 = stanzas.index(s1)
i2 = stanzas.index(s2)
end
i1 - i2
end
end
def stanza_order_index(stanza)
Constants::STANZA_ORDER.index(stanza.stanza_name)
end
end
end
end

View File

@ -23,6 +23,7 @@ module RuboCop
def_delegator :stanza_node, :parent, :parent_node
def_delegator :stanza_node, :arch_variable?
def_delegator :stanza_node, :on_system_block?
def source_range
stanza_node.location_expression
@ -48,6 +49,10 @@ module RuboCop
Constants::STANZA_GROUP_HASH[stanza_name]
end
def stanza_index
Constants::STANZA_ORDER.index(stanza_name)
end
def same_group?(other)
stanza_group == other.stanza_group
end
@ -65,7 +70,6 @@ module RuboCop
def ==(other)
self.class == other.class && stanza_node == other.stanza_node
end
alias eql? ==
Constants::STANZA_ORDER.each do |stanza_name|

View File

@ -14,11 +14,18 @@ module RuboCop
def_node_matcher :key_node, "{(pair $_ _) (hash (pair $_ _) ...)}"
def_node_matcher :val_node, "{(pair _ $_) (hash (pair _ $_) ...)}"
def_node_matcher :cask_block?, "(block (send nil? :cask _) args ...)"
def_node_matcher :cask_block?, "(block (send nil? :cask ...) args ...)"
def_node_matcher :on_system_block?,
"(block (send nil? {#{ON_SYSTEM_METHODS.map(&:inspect).join(" ")}} ...) args ...)"
def_node_matcher :arch_variable?, "(lvasgn _ (send nil? :on_arch_conditional ...))"
def_node_matcher :begin_block?, "(begin ...)"
sig { returns(T::Boolean) }
def cask_on_system_block?
(on_system_block? && each_ancestor.any?(&:cask_block?)) || false
end
def stanza?
return true if arch_variable?

View File

@ -6,22 +6,46 @@ module RuboCop
module Cask
# Common functionality for cops checking casks.
module CaskHelp
extend T::Helpers
prepend CommentsHelp
abstract!
sig { abstract.params(cask_block: RuboCop::Cask::AST::CaskBlock).void }
sig { overridable.params(cask_block: RuboCop::Cask::AST::CaskBlock).void }
def on_cask(cask_block); end
sig { overridable.params(cask_stanza_block: RuboCop::Cask::AST::StanzaBlock).void }
def on_cask_stanza_block(cask_stanza_block); end
# FIXME: Workaround until https://github.com/rubocop/rubocop/pull/11858 is released.
def find_end_line(node)
return node.loc.end.line if node.block_type? || node.numblock_type?
super
end
sig { params(block_node: RuboCop::AST::BlockNode).void }
def on_block(block_node)
super if defined? super
return unless respond_to?(:on_cask)
return if !block_node.cask_block? && !block_node.cask_on_system_block?
comments = comments_in_range(block_node).to_a
stanza_block = RuboCop::Cask::AST::StanzaBlock.new(block_node, comments)
on_cask_stanza_block(stanza_block)
return unless block_node.cask_block?
comments = processed_source.comments
cask_block = RuboCop::Cask::AST::CaskBlock.new(block_node, comments)
on_cask(cask_block)
end
def on_system_methods(cask_stanzas)
cask_stanzas.select(&:on_system_block?)
end
def inner_stanzas(block_node, comments)
block_contents = block_node.child_nodes.select(&:begin_type?)
inner_nodes = block_contents.map(&:child_nodes).flatten.select(&:send_type?)
inner_nodes.map { |n| RuboCop::Cask::AST::Stanza.new(n, comments) }
end
end
end
end

View File

@ -7,7 +7,6 @@ module RuboCop
class NoOverrides < Base
include CaskHelp
ON_SYSTEM_METHODS = RuboCop::Cask::Constants::ON_SYSTEM_METHODS
# These stanzas can be overridden by `on_*` blocks, so take them into account.
# TODO: Update this list if new stanzas are added to `Cask::DSL` that call `set_unique_stanza`.
OVERRIDEABLE_METHODS = [
@ -22,8 +21,7 @@ module RuboCop
def on_cask(cask_block)
cask_stanzas = cask_block.toplevel_stanzas
# Skip if there are no `on_*` blocks.
return if (on_blocks = cask_stanzas.select { |s| ON_SYSTEM_METHODS.include?(s.stanza_name) }).none?
return if (on_blocks = on_system_methods(cask_stanzas)).none?
stanzas_in_blocks = on_system_stanzas(on_blocks)
@ -40,9 +38,7 @@ module RuboCop
def on_system_stanzas(on_system)
names = Set.new
method_nodes = on_system.map(&:method_node)
method_nodes.each do |node|
next unless node.block_type?
method_nodes.select(&:block_type?).each do |node|
node.child_nodes.each do |child|
child.each_node(:send) do |send_node|
# Skip (nested) livecheck blocks as its `url` is different to a download `url`.
@ -51,7 +47,7 @@ module RuboCop
if send_node.ancestors.drop_while { |a| !a.begin_type? }.any? { |a| a.dstr_type? || a.regexp_type? }
next
end
next if ON_SYSTEM_METHODS.include?(send_node.method_name)
next if RuboCop::Cask::Constants::ON_SYSTEM_METHODS.include?(send_node.method_name)
names.add(send_node.method_name)
end

View File

@ -6,7 +6,7 @@ require "forwardable"
module RuboCop
module Cop
module Cask
# This cop checks that a cask's stanzas are grouped correctly.
# This cop checks that a cask's stanzas are grouped correctly, including nested within `on_*` blocks.
# @see https://docs.brew.sh/Cask-Cookbook#stanza-order
class StanzaGrouping < Base
extend Forwardable
@ -14,7 +14,6 @@ module RuboCop
include CaskHelp
include RangeHelp
ON_SYSTEM_METHODS = RuboCop::Cask::Constants::ON_SYSTEM_METHODS
MISSING_LINE_MSG = "stanza groups should be separated by a single empty line"
EXTRA_LINE_MSG = "stanzas within the same group should have no lines between them"
@ -24,17 +23,11 @@ module RuboCop
cask_stanzas = cask_block.toplevel_stanzas
add_offenses(cask_stanzas)
# If present, check grouping of stanzas within `on_*` blocks.
return if (on_blocks = cask_stanzas.select { |s| ON_SYSTEM_METHODS.include?(s.stanza_name) }).none?
return if (on_blocks = on_system_methods(cask_stanzas)).none?
on_blocks.map(&:method_node).each do |on_block|
next unless on_block.block_type?
block_contents = on_block.child_nodes.select(&:begin_type?)
inner_nodes = block_contents.map(&:child_nodes).flatten.select(&:send_type?)
inner_stanzas = inner_nodes.map { |node| RuboCop::Cask::AST::Stanza.new(node, processed_source.comments) }
add_offenses(inner_stanzas)
on_blocks.map(&:method_node).select(&:block_type?).each do |on_block|
stanzas = inner_stanzas(on_block, processed_source.comments)
add_offenses(stanzas)
end
end

View File

@ -6,46 +6,68 @@ require "forwardable"
module RuboCop
module Cop
module Cask
# This cop checks that a cask's stanzas are ordered correctly.
# This cop checks that a cask's stanzas are ordered correctly, including nested within `on_*` blocks.
# @see https://docs.brew.sh/Cask-Cookbook#stanza-order
class StanzaOrder < Base
include IgnoredNode
extend Forwardable
extend AutoCorrector
include CaskHelp
MESSAGE = "`%<stanza>s` stanza out of order"
def on_cask(cask_block)
@cask_block = cask_block
add_offenses
end
def on_cask_stanza_block(stanza_block)
stanzas = stanza_block.stanzas
ordered_stanzas = sort_stanzas(stanzas)
private
return if stanzas == ordered_stanzas
attr_reader :cask_block
stanzas.zip(ordered_stanzas).each do |stanza_before, stanza_after|
next if stanza_before == stanza_after
def_delegators :cask_block, :cask_node, :toplevel_stanzas,
:sorted_toplevel_stanzas
add_offense(
stanza_before.method_node,
message: format(MESSAGE, stanza: stanza_before.stanza_name),
) do |corrector|
next if part_of_ignored_node?(stanza_before.method_node)
def add_offenses
offending_stanzas.each do |stanza|
message = format(MESSAGE, stanza: stanza.stanza_name)
add_offense(stanza.source_range_with_comments, message: message) do |corrector|
correct_stanza_index = toplevel_stanzas.index(stanza)
correct_stanza = sorted_toplevel_stanzas[correct_stanza_index]
corrector.replace(stanza.source_range_with_comments,
correct_stanza.source_with_comments)
corrector.replace(
stanza_before.source_range_with_comments,
stanza_after.source_with_comments,
)
# Ignore node so that nested content is not auto-corrected and clobbered.
ignore_node(stanza_before.method_node)
end
end
end
def offending_stanzas
stanza_pairs = toplevel_stanzas.zip(sorted_toplevel_stanzas)
stanza_pairs.each_with_object([]) do |stanza_pair, offending_stanzas|
stanza, sorted_stanza = *stanza_pair
offending_stanzas << stanza if stanza != sorted_stanza
def on_new_investigation
super
ignored_nodes.clear
end
private
def sort_stanzas(stanzas)
stanzas.sort do |stanza1, stanza2|
i1 = stanza1.stanza_index
i2 = stanza2.stanza_index
if i1 == i2
i1 = stanzas.index(stanza1)
i2 = stanzas.index(stanza2)
end
i1 - i2
end
end
def stanza_order_index(stanza)
stanza_name = stanza.respond_to?(:method_name) ? stanza.method_name : stanza.stanza_name
RuboCop::Cask::Constants::STANZA_ORDER.index(stanza_name)
end
end
end
end

View File

@ -6373,6 +6373,8 @@ class RuboCop::AST::Node
def method_node(param0=T.unsafe(nil)); end
def on_system_block?(param0=T.unsafe(nil)); end
def val_node(param0=T.unsafe(nil)); end
end
@ -6504,12 +6506,61 @@ class RuboCop::Cask::AST::Stanza
def zap?(); end
end
module RuboCop::Cop::Cask::CaskHelp
include ::RuboCop::Cop::CommentsHelp
end
module RuboCop::Cop::Cask::CaskHelp
extend ::T::Private::Abstract::Hooks
extend ::T::InterfaceWrapper::Helpers
end
class RuboCop::Cop::Cask::Desc
include ::RuboCop::Cop::CommentsHelp
end
class RuboCop::Cop::Cask::HomepageUrlTrailingSlash
include ::RuboCop::Cop::CommentsHelp
end
class RuboCop::Cop::Cask::NoDslVersion
include ::RuboCop::Cop::CommentsHelp
end
class RuboCop::Cop::Cask::NoOverrides
include ::RuboCop::Cop::CommentsHelp
end
module RuboCop::Cop::Cask::OnDescStanza
include ::RuboCop::Cop::CommentsHelp
end
module RuboCop::Cop::Cask::OnHomepageStanza
include ::RuboCop::Cop::CommentsHelp
end
class RuboCop::Cop::Cask::OnSystemConditionals
include ::RuboCop::Cop::CommentsHelp
end
module RuboCop::Cop::Cask::OnUrlStanza
include ::RuboCop::Cop::CommentsHelp
end
class RuboCop::Cop::Cask::StanzaGrouping
include ::RuboCop::Cop::CommentsHelp
end
class RuboCop::Cop::Cask::StanzaOrder
include ::RuboCop::Cop::CommentsHelp
end
class RuboCop::Cop::Cask::Url
include ::RuboCop::Cop::CommentsHelp
end
class RuboCop::Cop::Cask::Variables
include ::RuboCop::Cop::CommentsHelp
def variable_assignment(param0); end
end

View File

@ -27,11 +27,11 @@ module CaskCop
offenses = inspect_source(source)
expect(offenses.size).to eq(expected_offenses.size)
expected_offenses.zip(offenses).each do |expected, actual|
expect_offense(expected, actual)
expect_offense2(expected, actual)
end
end
def expect_offense(expected, actual)
def expect_offense2(expected, actual)
expect(actual.message).to eq(expected[:message])
expect(actual.severity).to eq(expected[:severity])
expect(actual.line).to eq(expected[:line])
@ -39,8 +39,24 @@ module CaskCop
expect(actual.location.source).to eq(expected[:source])
end
# TODO: Replace with `expect_correction` from `rubocop-rspec`.
def expect_autocorrected_source(source, correct_source)
new_source = autocorrect_source(source)
expect(new_source).to eq(Array(correct_source).join("\n"))
correct_source = Array(correct_source).join("\n")
current_source = source
# RuboCop runs auto-correction in a loop to handle nested offenses.
loop do
current_source = autocorrect_source(current_source)
if (ignored_nodes = cop.instance_variable_get(:@ignored_nodes)) && ignored_nodes.any?
ignored_nodes.clear
next
end
break
end
expect(current_source).to eq correct_source
end
end

View File

@ -3,11 +3,9 @@
require "rubocops/rubocop-cask"
require "test/rubocops/cask/shared_examples/cask_cop"
describe RuboCop::Cop::Cask::StanzaOrder do
describe RuboCop::Cop::Cask::StanzaOrder, :config do
include CaskCop
subject(:cop) { described_class.new }
context "when there is only one stanza" do
let(:source) do
<<~CASK
@ -55,13 +53,13 @@ describe RuboCop::Cop::Cask::StanzaOrder do
end
let(:expected_offenses) do
[{
message: "Cask/StanzaOrder: `sha256` stanza out of order",
message: "`sha256` stanza out of order",
severity: :convention,
line: 2,
column: 2,
source: "sha256 :no_check",
}, {
message: "Cask/StanzaOrder: `version` stanza out of order",
message: "`version` stanza out of order",
severity: :convention,
line: 3,
column: 2,
@ -95,19 +93,19 @@ describe RuboCop::Cop::Cask::StanzaOrder do
end
let(:expected_offenses) do
[{
message: "Cask/StanzaOrder: `version` stanza out of order",
message: "`version` stanza out of order",
severity: :convention,
line: 2,
column: 2,
source: "version :latest",
}, {
message: "Cask/StanzaOrder: `sha256` stanza out of order",
message: "`sha256` stanza out of order",
severity: :convention,
line: 3,
column: 2,
source: "sha256 :no_check",
}, {
message: "Cask/StanzaOrder: `arch` stanza out of order",
message: "`arch` stanza out of order",
severity: :convention,
line: 4,
column: 2,
@ -143,13 +141,13 @@ describe RuboCop::Cop::Cask::StanzaOrder do
end
let(:expected_offenses) do
[{
message: "Cask/StanzaOrder: `sha256` stanza out of order",
message: "`sha256` stanza out of order",
severity: :convention,
line: 3,
column: 2,
source: "sha256 :no_check",
}, {
message: "Cask/StanzaOrder: `on_arch_conditional` stanza out of order",
message: "`on_arch_conditional` stanza out of order",
severity: :convention,
line: 5,
column: 2,
@ -185,13 +183,13 @@ describe RuboCop::Cop::Cask::StanzaOrder do
end
let(:expected_offenses) do
[{
message: "Cask/StanzaOrder: `on_arch_conditional` stanza out of order",
message: "`on_arch_conditional` stanza out of order",
severity: :convention,
line: 2,
column: 2,
source: 'folder = on_arch_conditional arm: "darwin-arm64", intel: "darwin"',
}, {
message: "Cask/StanzaOrder: `arch` stanza out of order",
message: "`arch` stanza out of order",
severity: :convention,
line: 3,
column: 2,
@ -231,26 +229,26 @@ describe RuboCop::Cop::Cask::StanzaOrder do
end
let(:expected_offenses) do
[{
message: "Cask/StanzaOrder: `url` stanza out of order",
message: "`url` stanza out of order",
severity: :convention,
line: 2,
column: 2,
source: "url 'https://foo.brew.sh/foo.zip'",
}, {
message: "Cask/StanzaOrder: `uninstall` stanza out of order",
message: "`uninstall` stanza out of order",
severity: :convention,
line: 3,
column: 2,
source: "uninstall :quit => 'com.example.foo'," \
"\n :kext => 'com.example.foo.kext'",
}, {
message: "Cask/StanzaOrder: `version` stanza out of order",
message: "`version` stanza out of order",
severity: :convention,
line: 5,
column: 2,
source: "version :latest",
}, {
message: "Cask/StanzaOrder: `sha256` stanza out of order",
message: "`sha256` stanza out of order",
severity: :convention,
line: 7,
column: 2,
@ -458,197 +456,108 @@ describe RuboCop::Cop::Cask::StanzaOrder do
include_examples "autocorrects source"
end
# TODO: detect out-of-order stanzas in nested expressions
context "when stanzas are nested in a conditional expression" do
let(:source) do
<<~CASK
cask 'foo' do
if true
sha256 :no_check
version :latest
end
end
CASK
end
include_examples "does not report any offenses"
end
context "when `on_arch` blocks are out of order" do
context "when `on_arch` blocks and their contents are out of order" do
let(:source) do
<<~CASK
cask 'foo' do
on_intel do
url "https://foo.brew.sh/foo-intel.zip"
sha256 :no_check
version :latest
end
version :latest
sha256 :no_check
end
on_arm do
url "https://foo.brew.sh/foo-arm.zip"
sha256 :no_check
version :latest
end
sha256 :no_check
name "Foo"
url "https://foo.brew.sh/foo-arm.zip"
end
end
CASK
end
let(:expected_offenses) do
[{
message: "Cask/StanzaOrder: `on_intel` stanza out of order",
severity: :convention,
line: 2,
column: 2,
source: "on_intel do\n url \"https://foo.brew.sh/foo-intel.zip\"\n sha256 :no_check\n version :latest\n end", # rubocop:disable Layout/LineLength
}, {
message: "Cask/StanzaOrder: `on_arm` stanza out of order",
severity: :convention,
line: 8,
column: 2,
source: "on_arm do\n url \"https://foo.brew.sh/foo-arm.zip\"\n sha256 :no_check\n version :latest\n end", # rubocop:disable Layout/LineLength
}]
end
let(:correct_source) do
<<~CASK
cask 'foo' do
on_arm do
version :latest
sha256 :no_check
url "https://foo.brew.sh/foo-arm.zip"
sha256 :no_check
version :latest
end
on_intel do
version :latest
sha256 :no_check
url "https://foo.brew.sh/foo-intel.zip"
sha256 :no_check
version :latest
end
name "Foo"
end
CASK
end
include_examples "reports offenses"
include_examples "autocorrects source"
end
# TODO: detect out-of-order stanzas in nested expressions
context "when the on_arch and on_os stanzas are nested" do
let(:source) do
<<~CASK
cask 'foo' do
on_arm do
url "https://foo.brew.sh/foo-arm-all.zip"
sha256 :no_check
version :latest
end
on_intel do
on_ventura do
url "https://foo.brew.sh/foo-intel-ventura.zip"
sha256 :no_check
end
on_mojave do
url "https://foo.brew.sh/foo-intel-mojave.zip"
sha256 :no_check
end
on_catalina do
url "https://foo.brew.sh/foo-intel-catalina.zip"
sha256 :no_check
end
on_big_sur do
url "https://foo.brew.sh/foo-intel-big-sur.zip"
sha256 :no_check
end
version :latest
end
name "Foo"
it "registers an offense when `on_os` stanzas and their contents are out of order" do
expect_offense <<~CASK
cask "foo" do
on_ventura do
^^^^^^^^^^^^^ `on_ventura` stanza out of order
sha256 "abc123"
^^^^^^^^^^^^^^^ `sha256` stanza out of order
version :latest
^^^^^^^^^^^^^^^ `version` stanza out of order
url "https://foo.brew.sh/foo-ventura.zip"
end
CASK
end
include_examples "does not report any offenses"
end
context "when the on_os stanzas are out of order" do
let(:source) do
<<~CASK
cask "foo" do
on_ventura do
url "https://foo.brew.sh/foo-ventura.zip"
sha256 :no_check
end
on_catalina do
url "https://foo.brew.sh/foo-catalina.zip"
sha256 :no_check
end
on_mojave do
url "https://foo.brew.sh/foo-mojave.zip"
sha256 :no_check
end
on_big_sur do
url "https://foo.brew.sh/foo-big-sur.zip"
sha256 :no_check
end
name "Foo"
on_catalina do
sha256 "def456"
^^^^^^^^^^^^^^^ `sha256` stanza out of order
version "0.7"
^^^^^^^^^^^^^ `version` stanza out of order
url "https://foo.brew.sh/foo-catalina.zip"
end
CASK
end
let(:expected_offenses) do
[{
message: "Cask/StanzaOrder: `on_ventura` stanza out of order",
severity: :convention,
line: 2,
column: 2,
source: "on_ventura do\n url \"https://foo.brew.sh/foo-ventura.zip\"\n sha256 :no_check\n end",
}, {
message: "Cask/StanzaOrder: `on_mojave` stanza out of order",
severity: :convention,
line: 10,
column: 2,
source: "on_mojave do\n url \"https://foo.brew.sh/foo-mojave.zip\"\n sha256 :no_check\n end",
}, {
message: "Cask/StanzaOrder: `on_big_sur` stanza out of order",
severity: :convention,
line: 14,
column: 2,
source: "on_big_sur do\n url \"https://foo.brew.sh/foo-big-sur.zip\"\n sha256 :no_check\n end",
}]
end
let(:correct_source) do
<<~CASK
cask "foo" do
on_mojave do
url "https://foo.brew.sh/foo-mojave.zip"
sha256 :no_check
end
on_catalina do
url "https://foo.brew.sh/foo-catalina.zip"
sha256 :no_check
end
on_big_sur do
url "https://foo.brew.sh/foo-big-sur.zip"
sha256 :no_check
end
on_ventura do
url "https://foo.brew.sh/foo-ventura.zip"
sha256 :no_check
end
name "Foo"
on_mojave do
^^^^^^^^^^^^ `on_mojave` stanza out of order
version :latest
sha256 "ghi789"
url "https://foo.brew.sh/foo-mojave.zip"
end
CASK
end
on_big_sur do
^^^^^^^^^^^^^ `on_big_sur` stanza out of order
sha256 "jkl012"
^^^^^^^^^^^^^^^ `sha256` stanza out of order
version :latest
^^^^^^^^^^^^^^^ `version` stanza out of order
include_examples "reports offenses"
include_examples "autocorrects source"
url "https://foo.brew.sh/foo-big-sur.zip"
end
end
CASK
expect_correction <<~CASK
cask "foo" do
on_mojave do
version :latest
sha256 "ghi789"
url "https://foo.brew.sh/foo-mojave.zip"
end
on_catalina do
version "0.7"
sha256 "def456"
url "https://foo.brew.sh/foo-catalina.zip"
end
on_big_sur do
version :latest
sha256 "jkl012"
url "https://foo.brew.sh/foo-big-sur.zip"
end
on_ventura do
version :latest
sha256 "abc123"
url "https://foo.brew.sh/foo-ventura.zip"
end
end
CASK
end
end