diff --git a/Library/Homebrew/rubocops.rb b/Library/Homebrew/rubocops.rb index 26432eefb3..794be945d0 100644 --- a/Library/Homebrew/rubocops.rb +++ b/Library/Homebrew/rubocops.rb @@ -12,6 +12,8 @@ require "rubocop-rails" require "rubocop-rspec" require "rubocop-sorbet" +require "rubocops/unless_multiple_conditions" + require "rubocops/formula_desc" require "rubocops/components_order" require "rubocops/components_redundancy" @@ -32,6 +34,6 @@ require "rubocops/files" require "rubocops/keg_only" require "rubocops/version" require "rubocops/deprecate_disable" -require "rubocops/unless_multiple_conditions" +require "rubocops/bottle" require "rubocops/rubocop-cask" diff --git a/Library/Homebrew/rubocops/bottle.rb b/Library/Homebrew/rubocops/bottle.rb new file mode 100644 index 0000000000..e1e4cd01c5 --- /dev/null +++ b/Library/Homebrew/rubocops/bottle.rb @@ -0,0 +1,198 @@ +# typed: true +# frozen_string_literal: true + +require "rubocops/extend/formula" + +module RuboCop + module Cop + module FormulaAudit + # This cop audits the `bottle` block in formulae. + # + # @api private + class BottleFormat < FormulaCop + extend AutoCorrector + + def audit_formula(_node, _class_node, _parent_class_node, body_node) + bottle_node = find_block(body_node, :bottle) + return if bottle_node.nil? + + sha256_nodes = find_method_calls_by_name(bottle_node.body, :sha256) + cellar_node = find_node_method_by_name(bottle_node.body, :cellar) + cellar_source = cellar_node&.first_argument&.source + + if sha256_nodes.present? && cellar_node.present? + offending_node(cellar_node) + problem "`cellar` should be a parameter to `sha256`" do |corrector| + corrector.remove(range_by_whole_lines(cellar_node.source_range, include_final_newline: true)) + end + end + + sha256_nodes.each do |sha256_node| + sha256_hash = sha256_node.last_argument + sha256_pairs = sha256_hash.pairs + next if sha256_pairs.count != 1 + + sha256_pair = sha256_pairs.first + sha256_key = sha256_pair.key + sha256_value = sha256_pair.value + next unless sha256_value.sym_type? + + tag = sha256_value.value + digest_source = sha256_key.source + sha256_line = if cellar_source.present? + "sha256 cellar: #{cellar_source}, #{tag}: #{digest_source}" + else + "sha256 #{tag}: #{digest_source}" + end + + offending_node(sha256_node) + problem "`sha256` should use new syntax" do |corrector| + corrector.replace(sha256_node.source_range, sha256_line) + end + end + end + end + + # This cop audits the indentation of the bottle tags in the `bottle` block in formulae. + # + # @api private + class BottleTagIndentation < FormulaCop + extend AutoCorrector + + def audit_formula(_node, _class_node, _parent_class_node, body_node) + bottle_node = find_block(body_node, :bottle) + return if bottle_node.nil? + + sha256_nodes = find_method_calls_by_name(bottle_node.body, :sha256) + + max_tag_column = 0 + sha256_nodes.each do |sha256_node| + sha256_hash = sha256_node.last_argument + tag_column = T.let(sha256_hash.pairs.last.source_range.column, Integer) + + max_tag_column = tag_column if tag_column > max_tag_column + end + # This must be in a separate loop to make sure max_tag_column is truly the maximum + sha256_nodes.each do |sha256_node| # rubocop:disable Style/CombinableLoops + sha256_hash = sha256_node.last_argument + hash = sha256_hash.pairs.last + tag_column = hash.source_range.column + + next if tag_column == max_tag_column + + offending_node(hash) + problem "Align bottle tags" do |corrector| + new_line = " " * (max_tag_column - tag_column) + hash.source + corrector.replace(hash.source_range, new_line) + end + end + end + end + + # This cop audits the indentation of the sha256 digests in the`bottle` block in formulae. + # + # @api private + class BottleDigestIndentation < FormulaCop + extend AutoCorrector + + def audit_formula(_node, _class_node, _parent_class_node, body_node) + bottle_node = find_block(body_node, :bottle) + return if bottle_node.nil? + + sha256_nodes = find_method_calls_by_name(bottle_node.body, :sha256) + + max_digest_column = 0 + sha256_nodes.each do |sha256_node| + sha256_hash = sha256_node.last_argument + digest_column = T.let(sha256_hash.pairs.last.value.source_range.column, Integer) + + max_digest_column = digest_column if digest_column > max_digest_column + end + # This must be in a separate loop to make sure max_digest_column is truly the maximum + sha256_nodes.each do |sha256_node| # rubocop:disable Style/CombinableLoops + sha256_hash = sha256_node.last_argument + hash = sha256_hash.pairs.last.value + digest_column = hash.source_range.column + + next if digest_column == max_digest_column + + offending_node(hash) + problem "Align bottle digests" do |corrector| + new_line = " " * (max_digest_column - digest_column) + hash.source + corrector.replace(hash.source_range, new_line) + end + end + end + end + + # This cop audits the order of the `bottle` block in formulae. + # + # @api private + class BottleOrder < FormulaCop + extend AutoCorrector + + def audit_formula(_node, _class_node, _parent_class_node, body_node) + bottle_node = find_block(body_node, :bottle) + return if bottle_node.nil? + return if bottle_node.child_nodes.blank? + + non_sha256_nodes = [] + sha256_nodes = [] + + bottle_block_method_calls = if bottle_node.child_nodes.last.begin_type? + bottle_node.child_nodes.last.child_nodes + else + [bottle_node.child_nodes.last] + end + + bottle_block_method_calls.each do |node| + if node.method_name == :sha256 + sha256_nodes << node + else + non_sha256_nodes << node + end + end + + arm64_nodes = [] + intel_nodes = [] + + sha256_nodes.each do |node| + version = sha256_bottle_tag node + if version.to_s.start_with? "arm64" + arm64_nodes << node + else + intel_nodes << node + end + end + + return if sha256_order(sha256_nodes) == sha256_order(arm64_nodes + intel_nodes) + + offending_node(bottle_node) + problem "ARM bottles should be listed before Intel bottles" do |corrector| + lines = ["bottle do"] + lines += non_sha256_nodes.map { |node| " #{node.source}" } + lines += arm64_nodes.map { |node| " #{node.source}" } + lines += intel_nodes.map { |node| " #{node.source}" } + lines << " end" + corrector.replace(bottle_node.source_range, lines.join("\n")) + end + end + + def sha256_order(nodes) + nodes.map do |node| + sha256_bottle_tag node + end + end + + def sha256_bottle_tag(node) + hash_pair = node.last_argument.pairs.last + if hash_pair.key.sym_type? + hash_pair.key.value + else + hash_pair.value.value + end + end + end + end + end +end diff --git a/Library/Homebrew/rubocops/shared/helper_functions.rb b/Library/Homebrew/rubocops/shared/helper_functions.rb index 9d5b3e581a..63858e3697 100644 --- a/Library/Homebrew/rubocops/shared/helper_functions.rb +++ b/Library/Homebrew/rubocops/shared/helper_functions.rb @@ -113,7 +113,12 @@ module RuboCop def find_method_calls_by_name(node, method_name) return if node.nil? - node.each_child_node(:send).select { |method_node| method_name == method_node.method_name } + nodes = node.each_child_node(:send).select { |method_node| method_name == method_node.method_name } + + # The top level node can be a method + nodes << node if node.send_type? && node.method_name == method_name + + nodes end # Returns an array of method call nodes matching method_name in every descendant of node. diff --git a/Library/Homebrew/test/rubocops/bottle/bottle_digest_indentation_spec.rb b/Library/Homebrew/test/rubocops/bottle/bottle_digest_indentation_spec.rb new file mode 100644 index 0000000000..01e5c3e5b3 --- /dev/null +++ b/Library/Homebrew/test/rubocops/bottle/bottle_digest_indentation_spec.rb @@ -0,0 +1,128 @@ +# typed: false +# frozen_string_literal: true + +require "rubocops/bottle" + +describe RuboCop::Cop::FormulaAudit::BottleDigestIndentation do + subject(:cop) { described_class.new } + + it "reports no offenses for `bottle :uneeded`" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle :unneeded + end + RUBY + end + + it "reports no offenses for properly aligned digests in `bottle` blocks without cellars" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 arm64_big_sur: "aaaaaaaa" + sha256 big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + sha256 arm64_big_sur: "aaaaaaaa" + end + end + RUBY + end + + it "reports no offenses for properly aligned tags in `bottle` blocks with cellars" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 cellar: :any, arm64_big_sur: "aaaaaaaa" + sha256 cellar: "/usr/local/Cellar", big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + sha256 cellar: :any, arm64_big_sur: "aaaaaaaa" + end + end + RUBY + end + + it "reports and corrects misaligned digests in `bottle` block" do + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 arm64_big_sur: "aaaaaaaa" + sha256 big_sur: "faceb00c" + ^^^^^^^^^^ Align bottle digests + sha256 catalina: "deadbeef" + ^^^^^^^^^^ Align bottle digests + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 arm64_big_sur: "aaaaaaaa" + sha256 big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + end + + it "reports and corrects misaligned digests in `bottle` block with cellars" do + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 cellar: :any, arm64_big_sur: "aaaaaaaa" + sha256 cellar: "/usr/local/Cellar", big_sur: "faceb00c" + ^^^^^^^^^^ Align bottle digests + sha256 catalina: "deadbeef" + ^^^^^^^^^^ Align bottle digests + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 cellar: :any, arm64_big_sur: "aaaaaaaa" + sha256 cellar: "/usr/local/Cellar", big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + end +end diff --git a/Library/Homebrew/test/rubocops/bottle/bottle_format_spec.rb b/Library/Homebrew/test/rubocops/bottle/bottle_format_spec.rb new file mode 100644 index 0000000000..2a080b0ab3 --- /dev/null +++ b/Library/Homebrew/test/rubocops/bottle/bottle_format_spec.rb @@ -0,0 +1,148 @@ +# typed: false +# frozen_string_literal: true + +require "rubocops/bottle" + +describe RuboCop::Cop::FormulaAudit::BottleFormat do + subject(:cop) { described_class.new } + + it "reports no offenses for `bottle :uneeded`" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle :unneeded + end + RUBY + end + + it "reports and corrects old `sha256` syntax in `bottle` block without cellars" do + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + sha256 "faceb00c" => :big_sur + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `sha256` should use new syntax + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + sha256 big_sur: "faceb00c" + end + end + RUBY + + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 "faceb00c" => :big_sur + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `sha256` should use new syntax + sha256 "deadbeef" => :catalina + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `sha256` should use new syntax + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + end + + it "reports and corrects old `sha256` syntax in `bottle` block without cellars" do + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + cellar :any + ^^^^^^^^^^^ `cellar` should be a parameter to `sha256` + rebuild 4 + sha256 "faceb00c" => :big_sur + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `sha256` should use new syntax + sha256 "deadbeef" => :catalina + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `sha256` should use new syntax + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 cellar: :any, big_sur: "faceb00c" + sha256 cellar: :any, catalina: "deadbeef" + end + end + RUBY + + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + cellar :any + ^^^^^^^^^^^ `cellar` should be a parameter to `sha256` + sha256 "faceb00c" => :big_sur + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `sha256` should use new syntax + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + sha256 cellar: :any, big_sur: "faceb00c" + end + end + RUBY + + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + cellar "/usr/local/Cellar" + ^^^^^^^^^^^^^^^^^^^^^^^^^^ `cellar` should be a parameter to `sha256` + rebuild 4 + sha256 "faceb00c" => :big_sur + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `sha256` should use new syntax + sha256 "deadbeef" => :catalina + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `sha256` should use new syntax + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 cellar: "/usr/local/Cellar", big_sur: "faceb00c" + sha256 cellar: "/usr/local/Cellar", catalina: "deadbeef" + end + end + RUBY + end +end diff --git a/Library/Homebrew/test/rubocops/bottle/bottle_order_spec.rb b/Library/Homebrew/test/rubocops/bottle/bottle_order_spec.rb new file mode 100644 index 0000000000..356a6ac48e --- /dev/null +++ b/Library/Homebrew/test/rubocops/bottle/bottle_order_spec.rb @@ -0,0 +1,240 @@ +# typed: false +# frozen_string_literal: true + +require "rubocops/bottle" + +describe RuboCop::Cop::FormulaAudit::BottleOrder do + subject(:cop) { described_class.new } + + it "reports no offenses for `bottle :uneeded`" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle :unneeded + end + RUBY + end + + it "reports no offenses for a properly ordered bottle block" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 arm64_something_else: "aaaaaaaa" + sha256 arm64_big_sur: "aaaaaaaa" + sha256 big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 cellar: :any, arm64_something_else: "aaaaaaaa" + sha256 cellar: :any_skip_relocation, arm64_big_sur: "aaaaaaaa" + sha256 cellar: "/usr/local/Cellar", big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + end + + it "reports no offenses for a properly ordered bottle block with a single bottle" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + sha256 big_sur: "faceb00c" + end + end + RUBY + + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + sha256 cellar: :any, big_sur: "faceb00c" + end + end + RUBY + end + + it "reports no offenses for a properly ordered bottle block with only arm/intel bottles" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 arm64_catalina: "aaaaaaaa" + sha256 arm64_big_sur: "aaaaaaaa" + end + end + RUBY + + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 arm64_big_sur: "aaaaaaaa" + end + end + RUBY + + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 big_sur: "faceb00c" + end + end + RUBY + end + + it "reports and corrects arm bottles below intel bottles" do + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + ^^^^^^^^^ ARM bottles should be listed before Intel bottles + rebuild 4 + sha256 big_sur: "faceb00c" + sha256 catalina: "deadbeef" + sha256 arm64_big_sur: "aaaaaaaa" + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 arm64_big_sur: "aaaaaaaa" + sha256 big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + end + + it "reports and corrects multiple arm bottles below intel bottles" do + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + ^^^^^^^^^ ARM bottles should be listed before Intel bottles + rebuild 4 + sha256 big_sur: "faceb00c" + sha256 arm64_catalina: "aaaaaaaa" + sha256 catalina: "deadbeef" + sha256 arm64_big_sur: "aaaaaaaa" + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 arm64_catalina: "aaaaaaaa" + sha256 arm64_big_sur: "aaaaaaaa" + sha256 big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + end + + it "reports and corrects arm bottles with cellars below intel bottles" do + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + ^^^^^^^^^ ARM bottles should be listed before Intel bottles + rebuild 4 + sha256 cellar: "/usr/local/Cellar", big_sur: "faceb00c" + sha256 catalina: "deadbeef" + sha256 cellar: :any, arm64_big_sur: "aaaaaaaa" + sha256 cellar: :any_skip_relocation, arm64_catalina: "aaaaaaaa" + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 cellar: :any, arm64_big_sur: "aaaaaaaa" + sha256 cellar: :any_skip_relocation, arm64_catalina: "aaaaaaaa" + sha256 cellar: "/usr/local/Cellar", big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + end + + it "reports and corrects arm bottles below intel bottles with old bottle syntax" do + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + ^^^^^^^^^ ARM bottles should be listed before Intel bottles + cellar :any + sha256 "faceb00c" => :big_sur + sha256 "aaaaaaaa" => :arm64_big_sur + sha256 "aaaaaaaa" => :arm64_catalina + sha256 "deadbeef" => :catalina + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + cellar :any + sha256 "aaaaaaaa" => :arm64_big_sur + sha256 "aaaaaaaa" => :arm64_catalina + sha256 "faceb00c" => :big_sur + sha256 "deadbeef" => :catalina + end + end + RUBY + end +end diff --git a/Library/Homebrew/test/rubocops/bottle/bottle_tag_indentation_spec.rb b/Library/Homebrew/test/rubocops/bottle/bottle_tag_indentation_spec.rb new file mode 100644 index 0000000000..c094c8d48e --- /dev/null +++ b/Library/Homebrew/test/rubocops/bottle/bottle_tag_indentation_spec.rb @@ -0,0 +1,98 @@ +# typed: false +# frozen_string_literal: true + +require "rubocops/bottle" + +describe RuboCop::Cop::FormulaAudit::BottleTagIndentation do + subject(:cop) { described_class.new } + + it "reports no offenses for `bottle :uneeded`" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle :unneeded + end + RUBY + end + + it "reports no offenses for `bottle` blocks without cellars" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 arm64_big_sur: "aaaaaaaa" + sha256 big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + sha256 big_sur: "faceb00c" + end + end + RUBY + end + + it "reports no offenses for properly aligned tags in `bottle` blocks with cellars" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 cellar: :any, arm64_big_sur: "aaaaaaaa" + sha256 cellar: "/usr/local/Cellar", big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + + expect_no_offenses(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + sha256 cellar: :any, arm64_big_sur: "aaaaaaaa" + end + end + RUBY + end + + it "reports and corrects misaligned tags in `bottle` block with cellars" do + expect_offense(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 cellar: :any, arm64_big_sur: "aaaaaaaa" + ^^^^^^^^^^^^^^^^^^^^^^^^^ Align bottle tags + sha256 cellar: "/usr/local/Cellar", big_sur: "faceb00c" + sha256 catalina: "deadbeef" + ^^^^^^^^^^^^^^^^^^^^ Align bottle tags + end + end + RUBY + + expect_correction(<<~RUBY) + class Foo < Formula + url "https://brew.sh/foo-1.0.tgz" + + bottle do + rebuild 4 + sha256 cellar: :any, arm64_big_sur: "aaaaaaaa" + sha256 cellar: "/usr/local/Cellar", big_sur: "faceb00c" + sha256 catalina: "deadbeef" + end + end + RUBY + end +end