# frozen_string_literal: true module RuboCop module Cop module Style # This cop checks for redundant `if` with boolean literal branches. # It checks only conditions to return boolean value (`true` or `false`) for safe detection. # The conditions to be checked are comparison methods, predicate methods, and double negative. # However, auto-correction is unsafe because there is no guarantee that all predicate methods # will return boolean value. Those methods can be allowed with `AllowedMethods` config. # # @example # # bad # if foo == bar # true # else # false # end # # # bad # foo == bar ? true : false # # # good # foo == bar # # @example AllowedMethods: ['nonzero?'] # # good # num.nonzero? ? true : false # class IfWithBooleanLiteralBranches < Base include AllowedMethods extend AutoCorrector MSG = 'Remove redundant %s with boolean literal branches.' MSG_FOR_ELSIF = 'Use `else` instead of redundant `elsif` with boolean literal branches.' def_node_matcher :if_with_boolean_literal_branches?, <<~PATTERN (if #return_boolean_value? {(true) (false) | (false) (true)}) PATTERN def_node_matcher :double_negative?, '(send (send _ :!) :!)' def on_if(node) return unless if_with_boolean_literal_branches?(node) condition = node.condition range, keyword = offense_range_with_keyword(node, condition) add_offense(range, message: message(node, keyword)) do |corrector| replacement = replacement_condition(node, condition) if node.elsif? corrector.insert_before(node, "else\n") corrector.replace(node, "#{indent(node.if_branch)}#{replacement}") else corrector.replace(node, replacement) end end end private def offense_range_with_keyword(node, condition) if node.ternary? range = condition.source_range.end.join(node.source_range.end) [range, 'ternary operator'] else keyword = node.loc.keyword [keyword, "`#{keyword.source}`"] end end def message(node, keyword) message_template = node.elsif? ? MSG_FOR_ELSIF : MSG format(message_template, keyword: keyword) end def return_boolean_value?(condition) if condition.begin_type? return_boolean_value?(condition.children.first) elsif condition.or_type? return_boolean_value?(condition.lhs) && return_boolean_value?(condition.rhs) elsif condition.and_type? return_boolean_value?(condition.rhs) else assume_boolean_value?(condition) end end def assume_boolean_value?(condition) return false unless condition.send_type? return false if allowed_method?(condition.method_name) condition.comparison_method? || condition.predicate_method? || double_negative?(condition) end def replacement_condition(node, condition) bang = '!' if opposite_condition?(node) if bang && require_parentheses?(condition) "#{bang}(#{condition.source})" else "#{bang}#{condition.source}" end end def opposite_condition?(node) !node.unless? && node.if_branch.false_type? || node.unless? && node.if_branch.true_type? end def require_parentheses?(condition) condition.and_type? || condition.or_type? || condition.send_type? && condition.comparison_method? end end end end end