class RuboCop::Cop::Style::ConditionalAssignment

Check for `if` and `case` statements where each branch is used for assignment to the same variable when using the return of the condition can be used instead.

@example

EnforcedStyle: assign_to_condition

# bad
if foo
  bar = 1
else
  bar = 2
end

case foo
when 'a'
  bar += 1
else
  bar += 2
end

if foo
  some_method
  bar = 1
else
  some_other_method
  bar = 2
end

# good
bar = if foo
        1
      else
        2
      end

bar += case foo
       when 'a'
         1
       else
         2
       end

bar << if foo
         some_method
         1
       else
         some_other_method
         2
       end

EnforcedStyle: assign_inside_condition
# bad
bar = if foo
        1
      else
        2
      end

bar += case foo
       when 'a'
         1
       else
         2
       end

bar << if foo
         some_method
         1
       else
         some_other_method
         2
       end

# good
if foo
  bar = 1
else
  bar = 2
end

case foo
when 'a'
  bar += 1
else
  bar += 2
end

if foo
  some_method
  bar = 1
else
  some_other_method
  bar = 2
end

Constants

ASSIGNMENT_TYPES
ASSIGN_TO_CONDITION_MSG
CONDITION_TYPES
ENABLED
IF
INDENTATION_WIDTH
LINE_LENGTH
MAX
METHODS
MSG
SINGLE_LINE_CONDITIONS_ONLY
UNLESS
VARIABLE_ASSIGNMENT_TYPES
WIDTH

Public Instance Methods

autocorrect(node) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 266
def autocorrect(node)
  if assignment_type?(node)
    move_assignment_inside_condition(node)
  else
    move_assignment_outside_condition(node)
  end
end
check_assignment_to_condition(node) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 224
def check_assignment_to_condition(node)
  return unless style == :assign_inside_condition
  ignore_node(node)
  *_variable, assignment = *node
  if assignment.respond_to?(:type)
    if assignment.begin_type? && assignment.children.size == 1
      assignment, = *assignment
    end

    return unless CONDITION_TYPES.include?(assignment.type)
  end
  _condition, *branches, else_branch = *assignment
  return unless else_branch # empty else
  return if single_line_conditions_only? &&
            [*branches, else_branch].any?(&:begin_type?)
  add_offense(node, :expression, ASSIGN_TO_CONDITION_MSG)
end
on_case(node) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 255
def on_case(node)
  return unless style == :assign_to_condition
  _condition, *when_branches, else_branch = *node
  return unless else_branch # empty else

  when_branches = expand_when_branches(when_branches)
  branches = [*when_branches, else_branch]

  check_node(node, branches)
end
on_if(node) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 242
def on_if(node)
  return unless style == :assign_to_condition
  return if elsif?(node)

  _condition, if_branch, else_branch = *node
  elsif_branches, else_branch = expand_elses(else_branch)
  return unless else_branch # empty else

  branches = [if_branch, *elsif_branches, else_branch]

  check_node(node, branches)
end
on_send(node) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 219
def on_send(node)
  return unless assignment_type?(node)
  check_assignment_to_condition(node)
end

Private Instance Methods

assignment_type?(branch) click to toggle source

The shovel operator `<<` does not have its own type. It is a `send` type.

# File lib/rubocop/cop/style/conditional_assignment.rb, line 312
def assignment_type?(branch)
  return true if ASSIGNMENT_TYPES.include?(branch.type)

  if branch.send_type?
    _receiver, method, = *branch
    return true if METHODS.include?(method)
    return true if method.to_s.end_with?(EQUAL)
  end

  false
end
assignment_types_match?(*nodes) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 304
def assignment_types_match?(*nodes)
  return unless assignment_type?(nodes.first)
  first_type = nodes.first.type
  nodes.all? { |node| node.type == first_type }
end
check_node(node, branches) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 324
def check_node(node, branches)
  return unless branches.all?
  last_statements = branches.map { |branch| tail(branch) }
  return unless lhs_all_match?(last_statements)
  return if last_statements.any?(&:masgn_type?)
  return unless assignment_types_match?(*last_statements)

  return if single_line_conditions_only? && branches.any?(&:begin_type?)
  return if correction_exceeds_line_limit?(node, branches)

  add_offense(node, :expression)
end
correction_exceeds_line_limit?(node, branches) click to toggle source

If `Metrics/LineLength` is enabled, we do not want to introduce an offense by auto-correcting this cop. Find the max configured line length. Find the longest line of condition. Remove the assignment from lines that contain the offending assignment because after correcting, this will not be on the line anymore. Check if the length of the longest line + the length of the corrected assignment is greater than the max configured line length

# File lib/rubocop/cop/style/conditional_assignment.rb, line 344
def correction_exceeds_line_limit?(node, branches)
  return false unless config.for_cop(LINE_LENGTH)[ENABLED]
  assignment = lhs(tail(branches[0]))
  max_line_length = config.for_cop(LINE_LENGTH)[MAX]
  indentation_width = config.for_cop(INDENTATION_WIDTH)[WIDTH] || 2
  return true if longest_rhs(branches) + indentation_width +
                 assignment.length > max_line_length

  longest_line(node, assignment).length > max_line_length
end
lhs_all_match?(branches) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 299
def lhs_all_match?(branches)
  first_lhs = lhs(branches.first)
  branches.all? { |branch| lhs(branch) == first_lhs }
end
lines_with_numbers(node) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 368
def lines_with_numbers(node)
  line_nos = node.loc.line..node.loc.last_line
  node.source.lines.zip(line_nos)
end
longest_line(node, assignment) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 355
def longest_line(node, assignment)
  assignment_regex = /#{Regexp.escape(assignment).gsub(' ', '\s*')}/
  lines = node.source.lines.map do |line|
    line.chomp.sub(assignment_regex, '')
  end
  longest_line = lines.max_by(&:length)
  longest_line + assignment
end
longest_rhs(branches) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 364
def longest_rhs(branches)
  branches.map { |branch| branch.children.last.source.length }.max
end
move_assignment_inside_condition(node) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 288
def move_assignment_inside_condition(node)
  *_assignment, condition = *node
  if ternary?(condition) || ternary?(condition.children[0])
    TernaryCorrector.move_assignment_inside_condition(node)
  elsif condition.case_type?
    CaseCorrector.move_assignment_inside_condition(node)
  elsif condition.if_type?
    IfCorrector.move_assignment_inside_condition(node)
  end
end
move_assignment_outside_condition(node) click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 276
def move_assignment_outside_condition(node)
  if ternary?(node)
    TernaryCorrector.correct(node)
  elsif node.loc.keyword.is?(IF)
    IfCorrector.correct(self, node)
  elsif node.loc.keyword.is?(UNLESS)
    UnlessCorrector.correct(self, node)
  else
    CaseCorrector.correct(self, node)
  end
end
single_line_conditions_only?() click to toggle source
# File lib/rubocop/cop/style/conditional_assignment.rb, line 373
def single_line_conditions_only?
  cop_config[SINGLE_LINE_CONDITIONS_ONLY]
end