rubocops: add cops to check bottle format, indentation, and order

Co-authored-by: Rylan Polster <rslpolster@gmail.com>
Co-authored-by: Seeker <meaningseeking@protonmail.com>
This commit is contained in:
Seeker 2021-01-28 12:36:44 -08:00 committed by Rylan Polster
parent f33dc70e85
commit 4c42d717fd
No known key found for this signature in database
GPG Key ID: 46A744940CFF4D64
7 changed files with 821 additions and 2 deletions

View File

@ -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"

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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