More Sorbet typed: strict RuboCops

- Some of these I bumped to `typed: strict`, some of them I added
  intermediary type signatures to some of the methods to make my life
  easier in the (near, hopefully) future.
- Turns out that RuboCop node matchers that end in `?`
  can return `nil` if they don't match anything, not `false`.
This commit is contained in:
Issy Long 2025-02-01 23:54:35 +00:00
parent 4ee6e96bdf
commit 0fc1eb534b
No known key found for this signature in database
27 changed files with 260 additions and 115 deletions

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module RuboCop
@ -44,6 +44,7 @@ module RuboCop
)
PATTERN
sig { params(node: RuboCop::AST::Node).void }
def on_or(node)
nil_or_empty?(node) do |var1, var2|
return if var1 != var2
@ -57,14 +58,16 @@ module RuboCop
private
sig { params(corrector: RuboCop::Cop::Corrector, node: RuboCop::AST::Node).void }
def autocorrect(corrector, node)
variable1, _variable2 = nil_or_empty?(node)
range = node.source_range
corrector.replace(range, replacement(variable1))
end
sig { params(node: T.nilable(RuboCop::AST::Node)).returns(String) }
def replacement(node)
node.respond_to?(:source) ? "#{node.source}.blank?" : "blank?"
node.respond_to?(:source) ? "#{node&.source}.blank?" : "blank?"
end
end
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/extend/formula_cop"
@ -174,12 +174,14 @@ module RuboCop
end
end
sig { params(nodes: T::Array[RuboCop::AST::SendNode]).returns(T::Array[T.any(String, Symbol)]) }
def sha256_order(nodes)
nodes.map do |node|
sha256_bottle_tag node
end
end
sig { params(node: AST::SendNode).returns(T.any(String, Symbol)) }
def sha256_bottle_tag(node)
hash_pair = node.last_argument.pairs.last
if hash_pair.key.sym_type?

View File

@ -7,6 +7,7 @@ module RuboCop
class ArrayAlphabetization < Base
extend AutoCorrector
sig { params(node: RuboCop::AST::SendNode).void }
def on_send(node)
return unless [:zap, :uninstall].include?(node.method_name)

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/extend/formula_cop"
@ -23,6 +23,7 @@ module RuboCop
end
end
sig { params(checksum: T.nilable(RuboCop::AST::Node)).void }
def audit_sha256(checksum)
return if checksum.nil?
@ -37,7 +38,7 @@ module RuboCop
return unless regex_match_group(checksum, /[^a-f0-9]+/i)
add_offense(@offensive_source_range, message: "sha256 contains invalid characters")
add_offense(T.must(@offensive_source_range), message: "sha256 contains invalid characters")
end
end
@ -54,9 +55,9 @@ module RuboCop
next unless regex_match_group(checksum, /[A-F]+/)
add_offense(@offensive_source_range, message: "sha256 should be lowercase") do |corrector|
correction = @offensive_node.source.downcase
corrector.insert_before(@offensive_node.source_range, correction)
corrector.remove(@offensive_node.source_range)
correction = T.must(@offensive_node).source.downcase
corrector.insert_before(T.must(@offensive_node).source_range, correction)
corrector.remove(T.must(@offensive_node).source_range)
end
end
end

View File

@ -62,6 +62,7 @@ module RuboCop
(sym :blank?)))
PATTERN
sig { params(node: RuboCop::AST::SendNode).void }
def on_send(node)
return unless bad_method?(node)
@ -74,6 +75,7 @@ module RuboCop
private
sig { params(node: RuboCop::AST::SendNode).returns(T::Boolean) }
def bad_method?(node)
return true if reject_with_block_pass?(node)
@ -93,6 +95,7 @@ module RuboCop
arguments.length == 2 && arguments[1].source == receiver_in_block.source
end
sig { params(node: RuboCop::AST::SendNode).returns(Parser::Source::Range) }
def offense_range(node)
end_pos = if node.parent&.block_type? && node.parent&.send_node == node
node.parent.source_range.end_pos
@ -103,6 +106,7 @@ module RuboCop
range_between(node.loc.selector.begin_pos, end_pos)
end
sig { params(node: RuboCop::AST::SendNode).returns(String) }
def preferred_method(node)
node.method?(:reject) ? "compact_blank" : "compact_blank!"
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/extend/formula_cop"
@ -22,7 +22,7 @@ module RuboCop
reason = parameters(conflicts_with_call).last.values.first
offending_node(reason)
name = Regexp.new(@formula_name, Regexp::IGNORECASE)
name = Regexp.new(T.must(@formula_name), Regexp::IGNORECASE)
reason_text = string_content(reason).sub(name, "")
first_word = reason_text.split.first
@ -45,7 +45,7 @@ module RuboCop
if !tap_style_exception?(:versioned_formulae_conflicts_allowlist) && method_called_ever?(body_node,
:conflicts_with)
problem MSG do |corrector|
corrector.replace(@offensive_node.source_range, "keg_only :versioned_formula")
corrector.replace(T.must(@offensive_node).source_range, "keg_only :versioned_formula")
end
end
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/extend/formula_cop"
@ -61,13 +61,13 @@ module RuboCop
if reason_string.start_with?("it ")
problem "Do not start the reason with `it`" do |corrector|
corrector.replace(@offensive_node.source_range, "\"#{reason_string[3..]}\"")
corrector.replace(T.must(@offensive_node).source_range, "\"#{reason_string[3..]}\"")
end
end
if PUNCTUATION_MARKS.include?(reason_string[-1])
problem "Do not end the reason with a punctuation mark" do |corrector|
corrector.replace(@offensive_node.source_range, "\"#{reason_string.chop}\"")
corrector.replace(T.must(@offensive_node).source_range, "\"#{reason_string.chop}\"")
end
end
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/extend/formula_cop"
@ -17,7 +17,7 @@ module RuboCop
def audit_formula(formula_nodes)
body_node = formula_nodes.body_node
@name = @formula_name
@name = T.let(@formula_name, T.nilable(String))
desc_call = find_node_method_by_name(body_node, :desc)
offending_node(formula_nodes.class_node) if body_node.nil?
audit_desc(:formula, @name, desc_call)

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/shared/helper_functions"
@ -14,9 +14,10 @@ module RuboCop
abstract!
exclude_from_registry
sig { returns(T.nilable(String)) }
attr_accessor :file_path
@registry = Registry.global
@registry = T.let(Registry.global, RuboCop::Cop::Registry)
class FormulaNodes < T::Struct
prop :node, RuboCop::AST::ClassNode
@ -26,15 +27,18 @@ module RuboCop
end
# This method is called by RuboCop and is the main entry point.
sig { params(node: RuboCop::AST::ClassNode).void }
def on_class(node)
@file_path = processed_source.file_path
@file_path = T.let(processed_source.file_path, T.nilable(String))
return unless file_path_allowed?
return unless formula_class?(node)
class_node, parent_class_node, @body = *node
@formula_name = Pathname.new(@file_path).basename(".rb").to_s
@tap_style_exceptions = nil
audit_formula(FormulaNodes.new(node:, class_node:, parent_class_node:, body_node: @body))
class_node, parent_class_node, body = *node
@body = T.let(body, T.nilable(RuboCop::AST::Node))
@formula_name = T.let(Pathname.new(@file_path).basename(".rb").to_s, T.nilable(String))
@tap_style_exceptions = T.let(nil, T.nilable(T::Hash[Symbol, T::Array[String]]))
audit_formula(FormulaNodes.new(node:, class_node:, parent_class_node:, body_node: T.must(@body)))
end
sig { abstract.params(formula_nodes: FormulaNodes).void }
@ -44,7 +48,13 @@ module RuboCop
#
# @param urls [Array] url/mirror method call nodes
# @param regex [Regexp] pattern to match URLs
def audit_urls(urls, regex)
sig {
params(
urls: T::Array[RuboCop::AST::Node], regex: Regexp,
_block: T.proc.params(arg0: T::Array[RuboCop::AST::Node], arg1: String, arg2: Integer).void
).void
}
def audit_urls(urls, regex, &_block)
urls.each_with_index do |url_node, index|
url_string_node = parameters(url_node).first
url_string = string_content(url_string_node)
@ -59,6 +69,7 @@ module RuboCop
# Returns if the formula depends on dependency_name.
#
# @param dependency_name dependency's name
sig { params(dependency_name: T.any(String, Symbol), types: Symbol).returns(T::Boolean) }
def depends_on?(dependency_name, *types)
return false if @body.nil?
@ -69,13 +80,20 @@ module RuboCop
end
return false if idx.nil?
@offensive_node = dependency_nodes[idx]
@offensive_node = T.let(dependency_nodes[idx], T.nilable(RuboCop::AST::Node))
true
end
# Returns true if given dependency name and dependency type exist in given dependency method call node.
# TODO: Add case where key of hash is an array
sig {
params(
node: RuboCop::AST::Node, name: T.nilable(T.any(String, Symbol)), type: Symbol,
).returns(
T::Boolean,
)
}
def depends_on_name_type?(node, name = nil, type = :required)
name_match = !name # Match only by type when name is nil
@ -88,8 +106,8 @@ module RuboCop
name_match ||= dependency_name_hash_match?(node, name) if type_match
when :any
type_match = true
name_match ||= required_dependency_name?(node, name)
name_match ||= dependency_name_hash_match?(node, name)
name_match ||= required_dependency_name?(node, name) || false
name_match ||= dependency_name_hash_match?(node, name) || false
else
type_match = false
end
@ -115,13 +133,15 @@ module RuboCop
EOS
# Return all the caveats' string nodes in an array.
sig { returns(T::Array[RuboCop::AST::Node]) }
def caveats_strings
return [] if @body.nil?
find_strings(find_method_def(@body, :caveats))
find_strings(find_method_def(@body, :caveats)).to_a
end
# Returns the sha256 str node given a sha256 call node.
sig { params(call: RuboCop::AST::Node).returns(T.nilable(RuboCop::AST::Node)) }
def get_checksum_node(call)
return if parameters(call).empty? || parameters(call).nil?
@ -144,7 +164,8 @@ module RuboCop
end
# Yields to a block with comment text as parameter.
def audit_comments
sig { params(_block: T.proc.params(arg0: String).void).void }
def audit_comments(&_block)
processed_source.comments.each do |comment_node|
@offensive_node = comment_node
yield comment_node.text
@ -152,20 +173,26 @@ module RuboCop
end
# Returns true if the formula is versioned.
sig { returns(T::Boolean) }
def versioned_formula?
return false if @formula_name.nil?
@formula_name.include?("@")
end
# Returns the formula tap.
sig { returns(T.nilable(String)) }
def formula_tap
return unless (match_obj = @file_path.match(%r{/(homebrew-\w+)/}))
return unless (match_obj = @file_path&.match(%r{/(homebrew-\w+)/}))
match_obj[1]
end
# Returns the style exceptions directory from the file path.
sig { returns(T.nilable(String)) }
def style_exceptions_dir
file_directory = File.dirname(@file_path)
file_directory = File.dirname(@file_path) if @file_path
return unless file_directory
# if we're in a sharded subdirectory, look below that.
directory_name = File.basename(file_directory)
@ -189,6 +216,7 @@ module RuboCop
# Returns whether the given formula exists in the given style exception list.
# Defaults to the current formula being checked.
sig { params(list: Symbol, formula: T.nilable(String)).returns(T::Boolean) }
def tap_style_exception?(list, formula = nil)
if @tap_style_exceptions.nil? && !formula_tap.nil?
@tap_style_exceptions = {}
@ -209,11 +237,12 @@ module RuboCop
return false if @tap_style_exceptions.nil? || @tap_style_exceptions.count.zero?
return false unless @tap_style_exceptions.key? list
@tap_style_exceptions[list].include?(formula || @formula_name)
T.must(@tap_style_exceptions[list]).include?(formula || @formula_name)
end
private
sig { params(node: RuboCop::AST::Node).returns(T::Boolean) }
def formula_class?(node)
_, class_node, = *node
class_names = %w[
@ -223,19 +252,24 @@ module RuboCop
AmazonWebServicesFormula
]
class_node && class_names.include?(string_content(class_node))
!!(class_node && class_names.include?(string_content(class_node)))
end
sig { returns(T::Boolean) }
def file_path_allowed?
return true if @file_path.nil? # file_path is nil when source is directly passed to the cop, e.g. in specs
!@file_path.include?("/Library/Homebrew/test/")
end
sig { returns(T::Array[Symbol]) }
def on_system_methods
@on_system_methods ||= [:intel, :arm, :macos, :linux, :system, *MacOSVersion::SYMBOLS.keys].map do |m|
:"on_#{m}"
end
@on_system_methods ||= T.let(
[:intel, :arm, :macos, :linux, :system, *MacOSVersion::SYMBOLS.keys].map do |m|
:"on_#{m}"
end,
T.nilable(T::Array[Symbol]),
)
end
end
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module RuboCop
@ -10,6 +10,7 @@ module RuboCop
RESTRICT_ON_SEND = [:read, :readlines].freeze
sig { params(node: RuboCop::AST::SendNode).void }
def on_send(node)
return if node.receiver != s(:const, nil, :IO)
return if safe?(node.arguments.first)
@ -19,10 +20,11 @@ module RuboCop
private
sig { params(node: RuboCop::AST::Node).returns(T::Boolean) }
def safe?(node)
if node.str_type?
!node.str_content.empty? && !node.str_content.start_with?("|")
elsif node.dstr_type? || (node.send_type? && node.method?(:+))
elsif node.dstr_type? || (node.send_type? && T.cast(node, RuboCop::AST::SendNode).method?(:+))
safe?(node.children.first)
else
false

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/extend/formula_cop"
@ -29,24 +29,25 @@ module RuboCop
reason = parameters(keg_only_node).first
offending_node(reason)
name = Regexp.new(@formula_name, Regexp::IGNORECASE)
name = Regexp.new(T.must(@formula_name), Regexp::IGNORECASE)
reason = string_content(reason).sub(name, "")
first_word = reason.split.first
if /\A[A-Z]/.match?(reason) && !reason.start_with?(*allowlist)
problem "'#{first_word}' from the `keg_only` reason should be '#{first_word.downcase}'." do |corrector|
reason[0] = reason[0].downcase
corrector.replace(@offensive_node.source_range, "\"#{reason}\"")
corrector.replace(T.must(@offensive_node).source_range, "\"#{reason}\"")
end
end
return unless reason.end_with?(".")
problem "`keg_only` reason should not end with a period." do |corrector|
corrector.replace(@offensive_node.source_range, "\"#{reason.chop}\"")
corrector.replace(T.must(@offensive_node).source_range, "\"#{reason.chop}\"")
end
end
sig { params(node: RuboCop::AST::Node).void }
def autocorrect(node)
lambda do |corrector|
reason = string_content(node)

View File

@ -40,7 +40,7 @@ module RuboCop
return if begin_pos-end_pos == 3
problem "Use a space in class inheritance: " \
"class #{@formula_name.capitalize} < #{class_name(parent_class_node)}"
"class #{T.must(@formula_name).capitalize} < #{class_name(parent_class_node)}"
end
end
@ -181,9 +181,11 @@ module RuboCop
end
end
sig { params(node: RuboCop::AST::Node).returns(T::Boolean) }
def unless_modifier?(node)
return false unless node.if_type?
node = T.cast(node, RuboCop::AST::IfNode)
node.modifier_form? && node.unless?
end
@ -207,8 +209,8 @@ module RuboCop
find_method_with_args(body_node, :depends_on, "mpich") do
problem "Formulae in homebrew/core should use 'depends_on \"open-mpi\"' " \
"instead of '#{@offensive_node.source}'." do |corrector|
corrector.replace(@offensive_node.source_range, "depends_on \"open-mpi\"")
"instead of '#{T.must(@offensive_node).source}'." do |corrector|
corrector.replace(T.must(@offensive_node).source_range, "depends_on \"open-mpi\"")
end
end
end
@ -224,17 +226,19 @@ module RuboCop
return if (body_node = formula_nodes.body_node).nil?
find_method_with_args(body_node, :local_npm_install_args) do
problem "Use 'std_npm_args' instead of '#{@offensive_node.method_name}'." do |corrector|
corrector.replace(@offensive_node.source_range, "std_npm_args(prefix: false)")
problem "Use 'std_npm_args' instead of '#{T.cast(@offensive_node,
RuboCop::AST::SendNode).method_name}'." do |corrector|
corrector.replace(T.must(@offensive_node).source_range, "std_npm_args(prefix: false)")
end
end
find_method_with_args(body_node, :std_npm_install_args) do |method|
problem "Use 'std_npm_args' instead of '#{@offensive_node.method_name}'." do |corrector|
problem "Use 'std_npm_args' instead of '#{T.cast(@offensive_node,
RuboCop::AST::SendNode).method_name}'." do |corrector|
if (param = parameters(method).first.source) == "libexec"
corrector.replace(@offensive_node.source_range, "std_npm_args")
corrector.replace(T.must(@offensive_node).source_range, "std_npm_args")
else
corrector.replace(@offensive_node.source_range, "std_npm_args(prefix: #{param})")
corrector.replace(T.must(@offensive_node).source_range, "std_npm_args(prefix: #{param})")
end
end
end
@ -264,8 +268,8 @@ module RuboCop
find_method_with_args(body_node, :depends_on, "quictls") do
problem "Formulae in homebrew/core should use 'depends_on \"openssl@3\"' " \
"instead of '#{@offensive_node.source}'." do |corrector|
corrector.replace(@offensive_node.source_range, "depends_on \"openssl@3\"")
"instead of '#{T.must(@offensive_node).source}'." do |corrector|
corrector.replace(T.must(@offensive_node).source_range, "depends_on \"openssl@3\"")
end
end
end
@ -281,7 +285,7 @@ module RuboCop
return if formula_tap != "homebrew-core"
return unless depends_on?("pyoxidizer")
problem "Formulae in homebrew/core should not use '#{@offensive_node.source}'."
problem "Formulae in homebrew/core should not use '#{T.must(@offensive_node).source}'."
end
end
@ -307,7 +311,8 @@ module RuboCop
find_instance_method_call(body_node, "Utils", unsafe_command) do |method|
unless test_methods.include?(method.source_range)
problem "Use `Utils.safe_#{unsafe_command}` instead of `Utils.#{unsafe_command}`" do |corrector|
corrector.replace(@offensive_node.loc.selector, "safe_#{@offensive_node.method_name}")
corrector.replace(T.must(@offensive_node).loc.selector,
"safe_#{T.cast(@offensive_node, RuboCop::AST::SendNode).method_name}")
end
end
end
@ -478,7 +483,10 @@ module RuboCop
class MacOSOnLinux < FormulaCop
include OnSystemConditionalsHelper
ON_MACOS_BLOCKS = [:macos, *MACOS_VERSION_OPTIONS].map { |os| :"on_#{os}" }.freeze
ON_MACOS_BLOCKS = T.let(
[:macos, *MACOS_VERSION_OPTIONS].map { |os| :"on_#{os}" }.freeze,
T::Array[Symbol],
)
sig { override.params(formula_nodes: FormulaNodes).void }
def audit_formula(formula_nodes)
@ -529,8 +537,8 @@ module RuboCop
offending_node(node)
replacement = "generate_completions_from_executable(#{replacement_args.join(", ")})"
problem "Use `#{replacement}` instead of `#{@offensive_node.source}`." do |corrector|
corrector.replace(@offensive_node.source_range, replacement)
problem "Use `#{replacement}` instead of `#{T.must(@offensive_node).source}`." do |corrector|
corrector.replace(T.must(@offensive_node).source_range, replacement)
end
end
@ -539,7 +547,7 @@ module RuboCop
next if node.source.match?(/{.*=>.*}/) # skip commands needing custom ENV variables
offending_node(node)
problem "Use `generate_completions_from_executable` DSL instead of `#{@offensive_node.source}`."
problem "Use `generate_completions_from_executable` DSL instead of `#{T.must(@offensive_node).source}`."
end
end
@ -608,7 +616,7 @@ module RuboCop
problem "Use a single `generate_completions_from_executable` " \
"call combining all specified shells." do |corrector|
# adjust range by -4 and +1 to also include & remove leading spaces and trailing \n
corrector.replace(@offensive_node.source_range.adjust(begin_pos: -4, end_pos: 1), "")
corrector.replace(T.must(@offensive_node).source_range.adjust(begin_pos: -4, end_pos: 1), "")
end
end
@ -616,17 +624,18 @@ module RuboCop
offending_node(offenses.last)
replacement = if (%w[:bash :zsh :fish] - shells).empty?
@offensive_node.source.sub(/shells: \[(:bash|:zsh|:fish)\]/, "")
.sub(", )", ")") # clean up dangling trailing comma
.sub("(, ", "(") # clean up dangling leading comma
.sub(", , ", ", ") # clean up dangling enclosed comma
T.must(@offensive_node).source
.sub(/shells: \[(:bash|:zsh|:fish)\]/, "")
.sub(", )", ")") # clean up dangling trailing comma
.sub("(, ", "(") # clean up dangling leading comma
.sub(", , ", ", ") # clean up dangling enclosed comma
else
@offensive_node.source.sub(/shells: \[(:bash|:zsh|:fish)\]/,
"shells: [#{shells.join(", ")}]")
T.must(@offensive_node).source.sub(/shells: \[(:bash|:zsh|:fish)\]/,
"shells: [#{shells.join(", ")}]")
end
problem "Use `#{replacement}` instead of `#{@offensive_node.source}`." do |corrector|
corrector.replace(@offensive_node.source_range, replacement)
problem "Use `#{replacement}` instead of `#{T.must(@offensive_node).source}`." do |corrector|
corrector.replace(T.must(@offensive_node).source_range, replacement)
end
end
end
@ -783,7 +792,7 @@ module RuboCop
end
find_method_with_args(body_node, :system, /^(otool|install_name_tool|lipo)/) do
problem "Use ruby-macho instead of calling #{@offensive_node.source}"
problem "Use ruby-macho instead of calling #{T.must(@offensive_node).source}"
end
problem "Use new-style test definitions (test do)" if find_method_def(body_node, :test)
@ -854,10 +863,11 @@ module RuboCop
end
end
sig { params(node: RuboCop::AST::Node).returns(T::Boolean) }
def modifier?(node)
return false unless node.if_type?
node.modifier_form?
T.cast(node, RuboCop::AST::IfNode).modifier_form?
end
def_node_search :conditional_dependencies, <<~EOS
@ -892,7 +902,7 @@ module RuboCop
# Avoid build-time checks in homebrew/core
find_every_method_call_by_name(formula_nodes.body_node, :system).each do |method|
next if @formula_name.start_with?("lib")
next if @formula_name&.start_with?("lib")
next if tap_style_exception? :make_check_allowlist
params = parameters(method)
@ -933,8 +943,8 @@ module RuboCop
find_method_with_args(body_node, :depends_on, "rustup") do
problem "Formulae in homebrew/core should use 'depends_on \"rust\"' " \
"instead of '#{@offensive_node.source}'." do |corrector|
corrector.replace(@offensive_node.source_range, "depends_on \"rust\"")
"instead of '#{T.must(@offensive_node).source}'." do |corrector|
corrector.replace(T.must(@offensive_node).source_range, "depends_on \"rust\"")
end
end
@ -943,8 +953,8 @@ module RuboCop
[:build, [:build, :test], [:test, :build]].each do |type|
find_method_with_args(body_node, :depends_on, "rustup" => type) do
problem "Formulae in homebrew/core should use 'depends_on \"rust\" => #{type}' " \
"instead of '#{@offensive_node.source}'." do |corrector|
corrector.replace(@offensive_node.source_range, "depends_on \"rust\" => #{type}")
"instead of '#{T.must(@offensive_node).source}'." do |corrector|
corrector.replace(T.must(@offensive_node).source_range, "depends_on \"rust\" => #{type}")
end
end
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module RuboCop
@ -12,6 +12,7 @@ module RuboCop
(send (const nil? :OS) {:mac? | :linux?})
PATTERN
sig { params(node: RuboCop::AST::Node).void }
def on_send(node)
return unless os_check?(node)

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module RuboCop
@ -32,6 +32,7 @@ module RuboCop
(send (send $!nil? :include? $_) :!)
PATTERN
sig { params(node: RuboCop::AST::SendNode).void }
def on_send(node)
return unless (receiver, obj = negate_include_call?(node))

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/extend/formula_cop"
@ -14,7 +14,7 @@ module RuboCop
sig { override.params(formula_nodes: FormulaNodes).void }
def audit_formula(formula_nodes)
node = formula_nodes.node
@full_source_content = source_buffer(node).source
@full_source_content = T.let(source_buffer(node).source, T.nilable(String))
return if (body_node = formula_nodes.body_node).nil?
@ -43,6 +43,7 @@ module RuboCop
private
sig { params(patch_url_node: RuboCop::AST::Node).void }
def patch_problems(patch_url_node)
patch_url = string_content(patch_url_node)
@ -103,6 +104,7 @@ module RuboCop
end
end
sig { params(patch: RuboCop::AST::Node).void }
def inline_patch_problems(patch)
return if !patch_data?(patch) || patch_end?
@ -119,13 +121,17 @@ module RuboCop
/^__END__$/.match?(@full_source_content)
end
sig { params(node: RuboCop::AST::Node).void }
def offending_patch_end_node(node)
@offensive_node = node
@source_buf = source_buffer(node)
@line_no = node.loc.last_line + 1
@column = 0
@length = 7 # "__END__".size
@offense_source_range = source_range(@source_buf, @line_no, @column, @length)
@offensive_node = T.let(node, T.nilable(RuboCop::AST::Node))
@source_buf = T.let(source_buffer(node), T.nilable(Parser::Source::Buffer))
@line_no = T.let(node.loc.last_line + 1, T.nilable(Integer))
@column = T.let(0, T.nilable(Integer))
@length = T.let(7, T.nilable(Integer)) # "__END__".size
@offense_source_range = T.let(
source_range(@source_buf, @line_no, @column, @length),
T.nilable(Parser::Source::Range),
)
end
end
end

View File

@ -78,6 +78,7 @@ module RuboCop
}
PATTERN
sig { params(node: RuboCop::AST::IfNode).void }
def on_if(node)
return if ignore_if_node?(node)

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module RuboCop
@ -37,6 +37,7 @@ module RuboCop
)
PATTERN
sig { params(node: RuboCop::AST::AndNode).void }
def on_and(node)
exists_and_not_empty?(node) do |var1, var2|
return if var1 != var2
@ -49,6 +50,7 @@ module RuboCop
end
end
sig { params(node: RuboCop::AST::OrNode).void }
def on_or(node)
exists_and_not_empty?(node) do |var1, var2|
return if var1 != var2
@ -59,6 +61,7 @@ module RuboCop
end
end
sig { params(corrector: RuboCop::Cop::Corrector, node: RuboCop::AST::Node).void }
def autocorrect(corrector, node)
variable1, _variable2 = exists_and_not_empty?(node)
range = node.source_range
@ -67,8 +70,9 @@ module RuboCop
private
sig { params(node: T.nilable(RuboCop::AST::Node)).returns(String) }
def replacement(node)
node.respond_to?(:source) ? "#{node.source}.present?" : "present?"
node.respond_to?(:source) ? "#{node&.source}.present?" : "present?"
end
end
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
module RuboCop
@ -36,6 +36,7 @@ module RuboCop
(if $(csend ... :blank?) ...)
PATTERN
sig { params(node: RuboCop::AST::IfNode).void }
def on_if(node)
return unless safe_navigation_blank_in_conditional?(node)

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/extend/formula_cop"
@ -10,14 +10,17 @@ module RuboCop
class Service < FormulaCop
extend AutoCorrector
CELLAR_PATH_AUDIT_CORRECTIONS = {
bin: :opt_bin,
libexec: :opt_libexec,
pkgshare: :opt_pkgshare,
prefix: :opt_prefix,
sbin: :opt_sbin,
share: :opt_share,
}.freeze
CELLAR_PATH_AUDIT_CORRECTIONS = T.let(
{
bin: :opt_bin,
libexec: :opt_libexec,
pkgshare: :opt_pkgshare,
prefix: :opt_prefix,
sbin: :opt_sbin,
share: :opt_share,
}.freeze,
T::Hash[Symbol, Symbol],
)
# At least one of these methods must be defined in a service block.
REQUIRED_METHOD_CALLS = [:run, :name].freeze

View File

@ -17,6 +17,7 @@ module RuboCop
macOS
].freeze
sig { params(type: Symbol, name: T.nilable(String), desc_call: T.nilable(RuboCop::AST::Node)).void }
def audit_desc(type, name, desc_call)
# Check if a desc is present.
if desc_call.nil?
@ -26,7 +27,7 @@ module RuboCop
@offensive_node = desc_call
desc = desc_call.first_argument
desc = T.cast(desc_call, RuboCop::AST::SendNode).first_argument
# Check if the desc is empty.
desc_length = string_content(desc).length
@ -56,7 +57,7 @@ module RuboCop
end
# Check if the desc starts with the formula's or cask's name.
name_regex = name.delete("-").chars.join('[\s\-]?')
name_regex = T.must(name).delete("-").chars.join('[\s\-]?')
if regex_match_group(desc, /^#{name_regex}\b/i)
desc_problem "Description shouldn't start with the #{type} name."
end

View File

@ -32,7 +32,10 @@ module RuboCop
@line_no = line_number(node)
@source_buf = source_buffer(node)
@offensive_node = node
@offensive_source_range = source_range(@source_buf, @line_no, @column, @length)
@offensive_source_range = T.let(
source_range(@source_buf, @line_no, @column, @length),
T.nilable(Parser::Source::Range),
)
match_object
end
@ -53,6 +56,7 @@ module RuboCop
end
# Source buffer is required as an argument to report style violations.
sig { params(node: RuboCop::AST::Node).returns(Parser::Source::Buffer) }
def source_buffer(node)
node.source_range.source_buffer
end

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/shared/helper_functions"
@ -9,8 +9,15 @@ module RuboCop
module HomepageHelper
include HelperFunctions
sig {
params(
type: Symbol, content: String,
homepage_node: RuboCop::AST::Node,
homepage_parameter_node: RuboCop::AST::Node
).void
}
def audit_homepage(type, content, homepage_node, homepage_parameter_node)
@offensive_node = homepage_node
@offensive_node = T.let(homepage_node, T.nilable(RuboCop::AST::Node))
problem "#{type.to_s.capitalize} should have a homepage." if content.empty?

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "macos_version"
@ -13,16 +13,23 @@ module RuboCop
ARCH_OPTIONS = [:arm, :intel].freeze
BASE_OS_OPTIONS = [:macos, :linux].freeze
MACOS_VERSION_OPTIONS = MacOSVersion::SYMBOLS.keys.freeze
ON_SYSTEM_OPTIONS = [*ARCH_OPTIONS, *BASE_OS_OPTIONS, *MACOS_VERSION_OPTIONS, :system].freeze
MACOS_VERSION_OPTIONS = T.let(MacOSVersion::SYMBOLS.keys.freeze, T::Array[Symbol])
ON_SYSTEM_OPTIONS = T.let(
[*ARCH_OPTIONS, *BASE_OS_OPTIONS, *MACOS_VERSION_OPTIONS, :system].freeze,
T::Array[Symbol],
)
MACOS_MODULE_NAMES = ["MacOS", "OS::Mac"].freeze
MACOS_VERSION_CONDITIONALS = {
"==" => nil,
"<=" => :or_older,
">=" => :or_newer,
}.freeze
MACOS_VERSION_CONDITIONALS = T.let(
{
"==" => nil,
"<=" => :or_older,
">=" => :or_newer,
}.freeze,
T::Hash[String, T.nilable(Symbol)],
)
sig { params(body_node: RuboCop::AST::Node, parent_name: Symbol).void }
def audit_on_system_blocks(body_node, parent_name)
parent_string = if body_node.def_type?
"def #{parent_name}"
@ -77,6 +84,12 @@ module RuboCop
end
end
sig {
params(
body_node: RuboCop::AST::Node, allowed_methods: T::Array[Symbol],
allowed_blocks: T::Array[Symbol]
).void
}
def audit_arch_conditionals(body_node, allowed_methods: [], allowed_blocks: [])
ARCH_OPTIONS.each do |arch_option|
else_method = (arch_option == :arm) ? :on_intel : :on_arm
@ -100,6 +113,12 @@ module RuboCop
end
end
sig {
params(
body_node: RuboCop::AST::Node, allowed_methods: T::Array[Symbol],
allowed_blocks: T::Array[Symbol]
).void
}
def audit_base_os_conditionals(body_node, allowed_methods: [], allowed_blocks: [])
BASE_OS_OPTIONS.each do |base_os_option|
os_method, else_method = if base_os_option == :macos
@ -116,12 +135,21 @@ module RuboCop
end
end
sig {
params(
body_node: RuboCop::AST::Node,
allowed_methods: T::Array[Symbol],
allowed_blocks: T::Array[Symbol],
recommend_on_system: T::Boolean,
).void
}
def audit_macos_version_conditionals(body_node, allowed_methods: [], allowed_blocks: [],
recommend_on_system: true)
MACOS_VERSION_OPTIONS.each do |macos_version_option|
if_macos_version_node_search(body_node, os_version: macos_version_option) do |if_node, operator, else_node|
next if node_is_allowed?(if_node, allowed_methods:, allowed_blocks:)
else_node = T.let(else_node, T.nilable(RuboCop::AST::Node))
autocorrect = else_node.blank? && MACOS_VERSION_CONDITIONALS.key?(operator.to_s)
on_system_method_string = if recommend_on_system && operator == :<
"on_system"
@ -148,6 +176,13 @@ module RuboCop
end
end
sig {
params(
body_node: RuboCop::AST::Node,
allowed_methods: T::Array[Symbol],
allowed_blocks: T::Array[Symbol],
).void
}
def audit_macos_references(body_node, allowed_methods: [], allowed_blocks: [])
MACOS_MODULE_NAMES.each do |macos_module_name|
find_const(body_node, macos_module_name) do |node|
@ -161,6 +196,16 @@ module RuboCop
private
sig {
params(
if_node: RuboCop::AST::IfNode,
if_statement_string: String,
on_system_method_string: String,
else_method: T.nilable(Symbol),
else_node: T.nilable(RuboCop::AST::Node),
autocorrect: T::Boolean,
).void
}
def if_statement_problem(if_node, if_statement_string, on_system_method_string,
else_method: nil, else_node: nil, autocorrect: true)
offending_node(if_node)
@ -180,6 +225,12 @@ module RuboCop
end
end
sig {
params(
node: RuboCop::AST::Node, allowed_methods: T::Array[Symbol],
allowed_blocks: T::Array[Symbol]
).returns(T::Boolean)
}
def node_is_allowed?(node, allowed_methods: [], allowed_blocks: [])
# TODO: check to see if it's legal
valid = T.let(false, T::Boolean)

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "extend/array"
@ -64,8 +64,12 @@ module RuboCop
].freeze
private_constant :TARGET_METHODS
RESTRICT_ON_SEND = TARGET_METHODS.map(&:second).uniq.freeze
RESTRICT_ON_SEND = T.let(
TARGET_METHODS.map(&:second).uniq.freeze,
T::Array[T.nilable(Symbol)],
)
sig { params(node: RuboCop::AST::SendNode).void }
def on_send(node)
TARGET_METHODS.each do |target_class, target_method|
next if node.method_name != target_method
@ -119,6 +123,7 @@ module RuboCop
RESTRICT_ON_SEND = [:exec].freeze
sig { params(node: RuboCop::AST::SendNode).void }
def on_send(node)
return if node.receiver.present? && node.receiver != s(:const, nil, :Kernel)
return if node.arguments.count != 1

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/extend/formula_cop"
@ -133,7 +133,7 @@ module RuboCop
problem "`env :userpaths` in homebrew/core formulae is deprecated"
end
share_path_starts_with(body_node, @formula_name) do |share_node|
share_path_starts_with(body_node, T.must(@formula_name)) do |share_node|
offending_node(share_node)
problem "Use `pkgshare` instead of `share/\"#{@formula_name}\"`"
end
@ -162,11 +162,13 @@ module RuboCop
# Check whether value starts with the formula name and then a "/", " " or EOS.
# If we're checking for "#{bin}", we also check for "-" since similar binaries also don't need interpolation.
sig { params(path: String, starts_with: String, bin: T::Boolean).returns(T::Boolean) }
def path_starts_with?(path, starts_with, bin: false)
ending = bin ? "/|-|$" : "/| |$"
path.match?(/^#{Regexp.escape(starts_with)}(#{ending})/)
end
sig { params(path: String, starts_with: String).returns(T::Boolean) }
def path_starts_with_bin?(path, starts_with)
return false if path.include?(" ")

View File

@ -30,7 +30,7 @@ module RuboCop
# Check for binary URLs
audit_urls(urls, /(darwin|macos|osx)/i) do |match, url|
next if @formula_name.include?(match.to_s.downcase)
next if T.must(@formula_name).include?(match.to_s.downcase)
next if url.match?(/.(patch|diff)(\?full_index=1)?$/)
next if tap_style_exception? :not_a_binary_url_prefix_allowlist
next if tap_style_exception? :binary_bootstrap_formula_urls_allowlist

View File

@ -1,4 +1,4 @@
# typed: true # rubocop:todo Sorbet/StrictSigil
# typed: strict
# frozen_string_literal: true
require "rubocops/extend/formula_cop"