class RuboCop::Cop::Style::Next

Use `next` to skip iteration instead of a condition at the end.

@example

# bad
[1, 2].each do |a|
  if a == 1 do
    puts a
  end
end

# good
[1, 2].each do |a|
  next unless a == 1
  puts a
end

Constants

EACH_
ENUMERATORS
EXIT_TYPES
MSG

Public Instance Methods

investigate(_processed_source) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 34
def investigate(_processed_source)
  # When correcting nested offenses, we need to keep track of how much
  # we have adjusted the indentation of each line
  @reindented_lines = Hash.new(0)
end
on_block(node) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 40
def on_block(node)
  block_owner, _, body = *node
  return unless block_owner.send_type?
  return unless body && ends_with_condition?(body)

  _, method_name = *block_owner
  return unless enumerator?(method_name)

  offense_node = offense_node(body)
  add_offense(offense_node, offense_location(offense_node), MSG)
end
on_for(node) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 61
def on_for(node)
  _, _, body = *node
  return unless body && ends_with_condition?(body)

  offense_node = offense_node(body)
  add_offense(offense_node, offense_location(offense_node), MSG)
end
on_until(node)
Alias for: on_while
on_while(node) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 52
def on_while(node)
  _, body = *node
  return unless body && ends_with_condition?(body)

  offense_node = offense_node(body)
  add_offense(offense_node, offense_location(offense_node), MSG)
end
Also aliased as: on_until

Private Instance Methods

autocorrect(node) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 108
def autocorrect(node)
  lambda do |corrector|
    if modifier_if?(node)
      autocorrect_modifier(corrector, node)
    else
      autocorrect_block(corrector, node)
    end
  end
end
autocorrect_block(corrector, node) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 128
def autocorrect_block(corrector, node)
  cond, if_body, = *node

  next_code = "next #{opposite_kw(if_body)} #{cond.source}"
  corrector.insert_before(node.source_range, next_code)

  corrector.remove(cond_range(node, cond))
  corrector.remove(end_range(node))

  # end_range starts with the final newline of the if body
  reindent_lines = (node.source_range.line + 1)...node.loc.end.line
  reindent_lines = reindent_lines.to_a - heredoc_lines(node)
  reindent(reindent_lines, cond, corrector)
end
autocorrect_modifier(corrector, node) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 118
def autocorrect_modifier(corrector, node)
  cond, if_body, else_body = *node
  body = if_body || else_body

  replacement = "next #{opposite_kw(if_body)} #{cond.source}\n"                          "#{' ' * node.source_range.column}#{body.source}"

  corrector.replace(node.source_range, replacement)
end
cond_range(node, cond) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 147
def cond_range(node, cond)
  end_pos = if node.loc.begin
              node.loc.begin.end_pos # after "then"
            else
              cond.source_range.end_pos
            end
  Parser::Source::Range.new(node.source_range.source_buffer,
                            node.source_range.begin_pos,
                            end_pos)
end
end_followed_by_whitespace_only?(source_buffer, end_pos) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 168
def end_followed_by_whitespace_only?(source_buffer, end_pos)
  source_buffer.source[end_pos..-1] =~ /\A\s*$/
end
end_range(node) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 158
def end_range(node)
  source_buffer = node.source_range.source_buffer
  end_pos = node.loc.end.end_pos
  begin_pos = node.loc.end.begin_pos - node.source_range.column
  begin_pos -= 1 if end_followed_by_whitespace_only?(source_buffer,
                                                     end_pos)

  Parser::Source::Range.new(source_buffer, begin_pos, end_pos)
end
ends_with_condition?(body) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 76
def ends_with_condition?(body)
  return true if simple_if_without_break?(body)

  body.begin_type? && simple_if_without_break?(body.children.last)
end
enumerator?(method_name) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 71
def enumerator?(method_name)
  ENUMERATORS.include?(method_name) ||
    method_name.to_s.start_with?(EACH_)
end
heredoc_lines(node) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 202
def heredoc_lines(node)
  node.each_node(:dstr)
      .select { |n| n.loc.respond_to?(:heredoc_body) }
      .map { |n| n.loc.heredoc_body }
      .flat_map { |b| (b.line...b.last_line).to_a }
end
offense_location(offense_node) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 102
def offense_location(offense_node)
  condition_expression, = *offense_node
  offense_begin_pos = offense_node.source_range.begin
  offense_begin_pos.join(condition_expression.source_range)
end
offense_node(body) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 97
def offense_node(body)
  *_, condition = *body
  (condition && condition.if_type?) ? condition : body
end
opposite_kw(if_body) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 143
def opposite_kw(if_body)
  if_body.nil? ? 'if' : 'unless'
end
reindent(lines, node, corrector) click to toggle source

Adjust indentation of `lines` to match `node`

# File lib/rubocop/cop/style/next.rb, line 173
def reindent(lines, node, corrector)
  range  = node.source_range
  buffer = range.source_buffer

  target_indent = range.source_line =~ /\S/

  # Skip blank lines
  lines.reject! { |lineno| buffer.source_line(lineno) =~ /\A\s*\z/ }
  return if lines.empty?

  actual_indent = lines.map do |lineno|
    buffer.source_line(lineno) =~ /\S/
  end.min

  delta = actual_indent - target_indent
  lines.each do |lineno|
    adjustment = delta
    adjustment += @reindented_lines[lineno]
    @reindented_lines[lineno] = adjustment

    if adjustment > 0
      corrector.remove_leading(buffer.line_range(lineno), adjustment)
    elsif adjustment < 0
      corrector.insert_before(buffer.line_range(lineno),
                              ' ' * -adjustment)
    end
  end
end
simple_if_without_break?(node) click to toggle source
# File lib/rubocop/cop/style/next.rb, line 82
def simple_if_without_break?(node)
  return false unless node.if_type?
  return false if ternary_op?(node)
  return false if if_else?(node)
  return false if style == :skip_modifier_ifs && modifier_if?(node)
  return false if !modifier_if?(node) && !min_body_length?(node)

  # The `if` node must have only `if` body since we excluded `if` with
  # `else` above.
  _conditional, if_body, _else_body = *node
  return true unless if_body

  !EXIT_TYPES.include?(if_body.type)
end