2023-02-14 19:19:37 -08:00
|
|
|
# typed: true
|
2022-08-14 15:39:20 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
require "macos_versions"
|
|
|
|
require "rubocops/shared/helper_functions"
|
|
|
|
|
|
|
|
module RuboCop
|
|
|
|
module Cop
|
|
|
|
# This module performs common checks on `on_{system}` blocks in both formulae and casks.
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
module OnSystemConditionalsHelper
|
|
|
|
extend NodePattern::Macros
|
|
|
|
include HelperFunctions
|
|
|
|
|
|
|
|
ARCH_OPTIONS = [:arm, :intel].freeze
|
|
|
|
BASE_OS_OPTIONS = [:macos, :linux].freeze
|
|
|
|
MACOS_VERSION_OPTIONS = MacOSVersions::SYMBOLS.keys.freeze
|
|
|
|
ON_SYSTEM_OPTIONS = [*ARCH_OPTIONS, *BASE_OS_OPTIONS, *MACOS_VERSION_OPTIONS, :system].freeze
|
|
|
|
|
|
|
|
MACOS_VERSION_CONDITIONALS = {
|
|
|
|
"==" => nil,
|
|
|
|
"<=" => :or_older,
|
|
|
|
">=" => :or_newer,
|
|
|
|
}.freeze
|
|
|
|
|
|
|
|
def audit_on_system_blocks(body_node, parent_name)
|
|
|
|
parent_string = if body_node.def_type?
|
|
|
|
"def #{parent_name}"
|
|
|
|
else
|
|
|
|
"#{parent_name} do"
|
|
|
|
end
|
|
|
|
|
|
|
|
ON_SYSTEM_OPTIONS.each do |on_system_option|
|
|
|
|
on_system_method = :"on_#{on_system_option}"
|
|
|
|
if_statement_string = if ARCH_OPTIONS.include?(on_system_option)
|
|
|
|
"if Hardware::CPU.#{on_system_option}?"
|
|
|
|
elsif BASE_OS_OPTIONS.include?(on_system_option)
|
|
|
|
"if OS.#{(on_system_option == :macos) ? "mac" : "linux"}?"
|
|
|
|
elsif on_system_option == :system
|
|
|
|
"if OS.linux? || MacOS.version"
|
|
|
|
else
|
|
|
|
"if MacOS.version"
|
|
|
|
end
|
|
|
|
|
|
|
|
find_every_method_call_by_name(body_node, on_system_method).each do |on_system_node|
|
|
|
|
if_conditional = ""
|
|
|
|
if MACOS_VERSION_OPTIONS.include? on_system_option
|
|
|
|
on_macos_version_method_call(on_system_node, on_method: on_system_method) do |on_method_parameters|
|
|
|
|
if on_method_parameters.empty?
|
|
|
|
if_conditional = " == :#{on_system_option}"
|
|
|
|
else
|
|
|
|
if_condition_operator = MACOS_VERSION_CONDITIONALS.key(on_method_parameters.first)
|
|
|
|
if_conditional = " #{if_condition_operator} :#{on_system_option}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
elsif on_system_option == :system
|
|
|
|
on_system_method_call(on_system_node) do |macos_symbol|
|
|
|
|
base_os, condition = macos_symbol.to_s.split(/_(?=or_)/).map(&:to_sym)
|
|
|
|
if_condition_operator = MACOS_VERSION_CONDITIONALS.key(condition)
|
|
|
|
if_conditional = " #{if_condition_operator} :#{base_os}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
offending_node(on_system_node)
|
|
|
|
problem "Don't use `#{on_system_node.source}` in `#{parent_string}`, " \
|
|
|
|
"use `#{if_statement_string}#{if_conditional}` instead." do |corrector|
|
|
|
|
block_node = offending_node.parent
|
|
|
|
next if block_node.type != :block
|
|
|
|
|
|
|
|
# TODO: could fix corrector to handle this but punting for now.
|
|
|
|
next if block_node.single_line?
|
|
|
|
|
|
|
|
source_range = offending_node.source_range.join(offending_node.parent.loc.begin)
|
|
|
|
corrector.replace(source_range, "#{if_statement_string}#{if_conditional}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-26 18:25:33 +01:00
|
|
|
def audit_arch_conditionals(body_node, allowed_methods: [], allowed_blocks: [], optional_blocks: [])
|
2022-08-14 15:39:20 -04:00
|
|
|
ARCH_OPTIONS.each do |arch_option|
|
|
|
|
else_method = (arch_option == :arm) ? :on_intel : :on_arm
|
|
|
|
if_arch_node_search(body_node, arch: :"#{arch_option}?") do |if_node, else_node|
|
2023-03-26 18:25:33 +01:00
|
|
|
next if if_node_is_allowed?(
|
|
|
|
if_node,
|
|
|
|
allowed_methods: allowed_methods,
|
|
|
|
allowed_blocks: allowed_blocks,
|
|
|
|
optional_blocks: optional_blocks,
|
|
|
|
)
|
2022-08-14 15:39:20 -04:00
|
|
|
|
|
|
|
if_statement_problem(if_node, "if Hardware::CPU.#{arch_option}?", "on_#{arch_option}",
|
|
|
|
else_method: else_method, else_node: else_node)
|
|
|
|
end
|
|
|
|
end
|
2022-08-25 01:22:41 -04:00
|
|
|
|
|
|
|
[:arch, :arm?, :intel?].each do |method|
|
|
|
|
hardware_cpu_search(body_node, method: method) do |method_node|
|
2022-12-28 16:09:05 -05:00
|
|
|
# These should already be caught by `if_arch_node_search`
|
|
|
|
next if method_node.parent.source.start_with? "if #{method_node.source}"
|
2023-03-26 18:25:33 +01:00
|
|
|
next if if_node_is_allowed?(
|
|
|
|
method_node,
|
|
|
|
allowed_methods: allowed_methods,
|
|
|
|
allowed_blocks: allowed_blocks,
|
|
|
|
optional_blocks: optional_blocks,
|
|
|
|
)
|
2022-08-25 01:22:41 -04:00
|
|
|
|
|
|
|
offending_node(method_node)
|
|
|
|
problem "Don't use `#{method_node.source}`, use `on_arm` and `on_intel` blocks instead."
|
|
|
|
end
|
|
|
|
end
|
2022-08-14 15:39:20 -04:00
|
|
|
end
|
|
|
|
|
2023-03-26 18:25:33 +01:00
|
|
|
def audit_base_os_conditionals(body_node, allowed_methods: [], allowed_blocks: [], optional_blocks: [])
|
2022-08-14 15:39:20 -04:00
|
|
|
BASE_OS_OPTIONS.each do |base_os_option|
|
|
|
|
os_method, else_method = if base_os_option == :macos
|
|
|
|
[:mac?, :on_linux]
|
|
|
|
else
|
|
|
|
[:linux?, :on_macos]
|
|
|
|
end
|
|
|
|
if_base_os_node_search(body_node, base_os: os_method) do |if_node, else_node|
|
2023-03-26 18:25:33 +01:00
|
|
|
next if if_node_is_allowed?(
|
|
|
|
if_node,
|
|
|
|
allowed_methods: allowed_methods,
|
|
|
|
allowed_blocks: allowed_blocks,
|
|
|
|
optional_blocks: optional_blocks,
|
|
|
|
)
|
2022-08-14 15:39:20 -04:00
|
|
|
|
|
|
|
if_statement_problem(if_node, "if OS.#{os_method}", "on_#{base_os_option}",
|
|
|
|
else_method: else_method, else_node: else_node)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
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 if_node_is_allowed?(if_node, allowed_methods: allowed_methods, allowed_blocks: allowed_blocks)
|
|
|
|
|
|
|
|
autocorrect = else_node.blank? && MACOS_VERSION_CONDITIONALS.key?(operator.to_s)
|
|
|
|
on_system_method_string = if recommend_on_system && operator == :<
|
|
|
|
"on_system"
|
|
|
|
elsif recommend_on_system && operator == :<=
|
|
|
|
"on_system :linux, macos: :#{macos_version_option}_or_older"
|
|
|
|
elsif operator != :== && MACOS_VERSION_CONDITIONALS.key?(operator.to_s)
|
|
|
|
"on_#{macos_version_option} :#{MACOS_VERSION_CONDITIONALS[operator.to_s]}"
|
|
|
|
else
|
|
|
|
"on_#{macos_version_option}"
|
|
|
|
end
|
|
|
|
|
|
|
|
if_statement_problem(if_node, "if MacOS.version #{operator} :#{macos_version_option}",
|
|
|
|
on_system_method_string, autocorrect: autocorrect)
|
|
|
|
end
|
2022-08-25 03:41:15 -04:00
|
|
|
|
|
|
|
macos_version_comparison_search(body_node, os_version: macos_version_option) do |method_node|
|
2022-12-28 16:09:05 -05:00
|
|
|
# These should already be caught by `if_macos_version_node_search`
|
|
|
|
next if method_node.parent.source.start_with? "if #{method_node.source}"
|
2022-08-25 03:41:15 -04:00
|
|
|
next if if_node_is_allowed?(method_node, allowed_methods: allowed_methods, allowed_blocks: allowed_blocks)
|
|
|
|
|
|
|
|
offending_node(method_node)
|
|
|
|
problem "Don't use `#{method_node.source}`, use `on_{macos_version}` blocks instead."
|
|
|
|
end
|
2022-08-14 15:39:20 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
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)
|
|
|
|
problem "Don't use `#{if_statement_string}`, " \
|
|
|
|
"use `#{on_system_method_string} do` instead." do |corrector|
|
|
|
|
next unless autocorrect
|
|
|
|
# TODO: could fix corrector to handle this but punting for now.
|
|
|
|
next if if_node.unless?
|
|
|
|
|
|
|
|
if else_method.present? && else_node.present?
|
|
|
|
corrector.replace(if_node.source_range,
|
|
|
|
"#{on_system_method_string} do\n#{if_node.body.source}\nend\n" \
|
|
|
|
"#{else_method} do\n#{else_node.source}\nend")
|
|
|
|
else
|
|
|
|
corrector.replace(if_node.source_range, "#{on_system_method_string} do\n#{if_node.body.source}\nend")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-03-26 18:25:33 +01:00
|
|
|
def if_node_is_allowed?(if_node, allowed_methods: [], allowed_blocks: [], optional_blocks: [])
|
2022-08-14 15:39:20 -04:00
|
|
|
# TODO: check to see if it's legal
|
2023-02-14 19:19:37 -08:00
|
|
|
valid = T.let(false, T::Boolean)
|
2023-03-26 18:25:33 +01:00
|
|
|
allowed_and_optional_blocks = allowed_blocks + optional_blocks
|
2022-08-14 15:39:20 -04:00
|
|
|
if_node.each_ancestor do |ancestor|
|
|
|
|
valid_method_names = case ancestor.type
|
|
|
|
when :def
|
|
|
|
allowed_methods
|
|
|
|
when :block
|
2023-03-26 18:25:33 +01:00
|
|
|
allowed_and_optional_blocks
|
2022-08-14 15:39:20 -04:00
|
|
|
else
|
|
|
|
next
|
|
|
|
end
|
|
|
|
next unless valid_method_names.include?(ancestor.method_name)
|
|
|
|
|
|
|
|
valid = true
|
|
|
|
break
|
|
|
|
end
|
|
|
|
return true if valid
|
|
|
|
|
|
|
|
false
|
|
|
|
end
|
|
|
|
|
|
|
|
def_node_matcher :on_macos_version_method_call, <<~PATTERN
|
|
|
|
(send nil? %on_method (sym ${:or_newer :or_older})?)
|
|
|
|
PATTERN
|
|
|
|
|
|
|
|
def_node_matcher :on_system_method_call, <<~PATTERN
|
|
|
|
(send nil? :on_system (sym :linux) (hash (pair (sym :macos) (sym $_))))
|
|
|
|
PATTERN
|
|
|
|
|
2022-08-25 01:22:41 -04:00
|
|
|
def_node_search :hardware_cpu_search, <<~PATTERN
|
|
|
|
(send (const (const nil? :Hardware) :CPU) %method)
|
|
|
|
PATTERN
|
|
|
|
|
2022-08-25 03:41:15 -04:00
|
|
|
def_node_search :macos_version_comparison_search, <<~PATTERN
|
|
|
|
(send (send (const nil? :MacOS) :version) {:== :<= :< :>= :> :!=} (sym %os_version))
|
|
|
|
PATTERN
|
|
|
|
|
2022-08-14 15:39:20 -04:00
|
|
|
def_node_search :if_arch_node_search, <<~PATTERN
|
|
|
|
$(if (send (const (const nil? :Hardware) :CPU) %arch) _ $_)
|
|
|
|
PATTERN
|
|
|
|
|
|
|
|
def_node_search :if_base_os_node_search, <<~PATTERN
|
|
|
|
$(if (send (const nil? :OS) %base_os) _ $_)
|
|
|
|
PATTERN
|
|
|
|
|
|
|
|
def_node_search :if_macos_version_node_search, <<~PATTERN
|
|
|
|
$(if (send (send (const nil? :MacOS) :version) ${:== :<= :< :>= :> :!=} (sym %os_version)) _ $_)
|
|
|
|
PATTERN
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|