diff --git a/Library/Homebrew/rubocops/lines.rb b/Library/Homebrew/rubocops/lines.rb index 9f7dfe437a..ef4eaf0352 100644 --- a/Library/Homebrew/rubocops/lines.rb +++ b/Library/Homebrew/rubocops/lines.rb @@ -319,6 +319,43 @@ module RuboCop EOS end + # This cop makes sure that python versions are consistent. + # + # @api private + class PythonVersions < FormulaCop + def audit_formula(_node, _class_node, _parent_class_node, body_node) + python_formula_node = find_every_method_call_by_name(body_node, :depends_on).find do |dep| + string_content(parameters(dep).first).start_with? "python@" + end + + return if python_formula_node.blank? + + python_version = string_content(parameters(python_formula_node).first).split("@").last + + find_strings(body_node).each do |str| + string_content = string_content(str) + + next unless match = string_content.match(/^python(@)?(\d\.\d+)$/) + next if python_version == match[2] + + @fix = if match[1] + "python@#{python_version}" + else + "python#{python_version}" + end + + offending_node(str) + problem "References to `#{string_content}` should match the specified python dependency (`#{@fix}`)" + end + end + + def autocorrect(node) + lambda do |corrector| + corrector.replace(node.source_range, "\"#{@fix}\"") + end + end + end + # This cop checks for other miscellaneous style violations. # # @api private diff --git a/Library/Homebrew/test/rubocops/lines_spec.rb b/Library/Homebrew/test/rubocops/lines_spec.rb index fa61dd9a64..d9c117deb8 100644 --- a/Library/Homebrew/test/rubocops/lines_spec.rb +++ b/Library/Homebrew/test/rubocops/lines_spec.rb @@ -731,6 +731,246 @@ describe RuboCop::Cop::FormulaAudit::Licenses do end end +describe RuboCop::Cop::FormulaAudit::PythonVersions do + subject(:cop) { described_class.new } + + context "When auditing python versions" do + it "allow python with no dependency" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + def install + puts "python@3.8" + end + end + RUBY + end + + it "allow non versioned python references" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + depends_on "python@3.9" + + def install + puts "python" + end + end + RUBY + end + + it "allow python with no version" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + depends_on "python@3.9" + + def install + puts "python3" + end + end + RUBY + end + + it "allow matching versions" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + depends_on "python@3.9" + + def install + puts "python@3.9" + end + end + RUBY + end + + it "allow matching versions without `@`" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + depends_on "python@3.9" + + def install + puts "python3.9" + end + end + RUBY + end + + it "allow matching versions with two digits" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + depends_on "python@3.10" + + def install + puts "python@3.10" + end + end + RUBY + end + + it "allow matching versions without `@` with two digits" do + expect_no_offenses(<<~RUBY) + class Foo < Formula + depends_on "python@3.10" + + def install + puts "python3.10" + end + end + RUBY + end + + it "do not allow mismatching versions" do + expect_offense(<<~RUBY) + class Foo < Formula + depends_on "python@3.9" + + def install + puts "python@3.8" + ^^^^^^^^^^^^ References to `python@3.8` should match the specified python dependency (`python@3.9`) + end + end + RUBY + end + + it "do not allow mismatching versions without `@`" do + expect_offense(<<~RUBY) + class Foo < Formula + depends_on "python@3.9" + + def install + puts "python3.8" + ^^^^^^^^^^^ References to `python3.8` should match the specified python dependency (`python3.9`) + end + end + RUBY + end + + it "do not allow mismatching versions with two digits" do + expect_offense(<<~RUBY) + class Foo < Formula + depends_on "python@3.11" + + def install + puts "python@3.10" + ^^^^^^^^^^^^^ References to `python@3.10` should match the specified python dependency (`python@3.11`) + end + end + RUBY + end + + it "do not allow mismatching versions without `@` with two digits" do + expect_offense(<<~RUBY) + class Foo < Formula + depends_on "python@3.11" + + def install + puts "python3.10" + ^^^^^^^^^^^^ References to `python3.10` should match the specified python dependency (`python3.11`) + end + end + RUBY + end + + it "autocorrects mismatching versions" do + source = <<~RUBY + class Foo < Formula + depends_on "python@3.9" + + def install + puts "python@3.8" + end + end + RUBY + + corrected_source = <<~RUBY + class Foo < Formula + depends_on "python@3.9" + + def install + puts "python@3.9" + end + end + RUBY + + new_source = autocorrect_source(source) + expect(new_source).to eq(corrected_source) + end + + it "autocorrects mismatching versions without `@`" do + source = <<~RUBY + class Foo < Formula + depends_on "python@3.9" + + def install + puts "python3.8" + end + end + RUBY + + corrected_source = <<~RUBY + class Foo < Formula + depends_on "python@3.9" + + def install + puts "python3.9" + end + end + RUBY + + new_source = autocorrect_source(source) + expect(new_source).to eq(corrected_source) + end + + it "autocorrects mismatching versions with two digits" do + source = <<~RUBY + class Foo < Formula + depends_on "python@3.10" + + def install + puts "python@3.9" + end + end + RUBY + + corrected_source = <<~RUBY + class Foo < Formula + depends_on "python@3.10" + + def install + puts "python@3.10" + end + end + RUBY + + new_source = autocorrect_source(source) + expect(new_source).to eq(corrected_source) + end + + it "autocorrects mismatching versions without `@` with two digits" do + source = <<~RUBY + class Foo < Formula + depends_on "python@3.11" + + def install + puts "python3.10" + end + end + RUBY + + corrected_source = <<~RUBY + class Foo < Formula + depends_on "python@3.11" + + def install + puts "python3.11" + end + end + RUBY + + new_source = autocorrect_source(source) + expect(new_source).to eq(corrected_source) + end + end +end + describe RuboCop::Cop::FormulaAudit::Miscellaneous do subject(:cop) { described_class.new }