class RuboCop::Cop::Layout::BlockAlignment

Checks whether the end keywords are aligned properly for do end blocks.

Three modes are supported through the ‘EnforcedStyleAlignWith` configuration parameter:

‘start_of_block` : the `end` shall be aligned with the start of the line where the `do` appeared.

‘start_of_line` : the `end` shall be aligned with the start of the line where the expression started.

‘either` (which is the default) : the `end` is allowed to be in either location. The autocorrect will default to `start_of_line`.

@example EnforcedStyleAlignWith: either (default)

# bad

foo.bar
  .each do
    baz
      end

# good

foo.bar
  .each do
    baz
end

@example EnforcedStyleAlignWith: start_of_block

# bad

foo.bar
  .each do
    baz
      end

# good

foo.bar
  .each do
    baz
  end

@example EnforcedStyleAlignWith: start_of_line

# bad

foo.bar
  .each do
    baz
      end

# good

foo.bar
  .each do
    baz
end

Constants

MSG

Public Instance Methods

on_block(node) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 83
def on_block(node)
  check_block_alignment(start_for_block_node(node), node)
end
Also aliased as: on_numblock
on_numblock(node)
Alias for: on_block
style_parameter_name() click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 89
def style_parameter_name
  'EnforcedStyleAlignWith'
end

Private Instance Methods

add_space_before(corrector, loc, delta) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 246
def add_space_before(corrector, loc, delta)
  corrector.insert_before(loc, ' ' * delta)
end
alt_start_msg(start_loc, source_line_column) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 223
def alt_start_msg(start_loc, source_line_column)
  if style != :either ||
     (start_loc.line == source_line_column[:line] &&
         start_loc.column == source_line_column[:column])
    ''
  else
    " or #{format_source_line_column(source_line_column)}"
  end
end
autocorrect(corrector, node) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 145
def autocorrect(corrector, node)
  ancestor_node = if style == :start_of_block
                    start_for_block_node(node)
                  else
                    start_for_line_node(node)
                  end
  start_col = compute_start_col(ancestor_node, node)
  loc_end = node.loc.end
  delta = start_col - loc_end.column

  if delta.positive?
    add_space_before(corrector, loc_end, delta)
  elsif delta.negative?
    remove_space_before(corrector, loc_end.begin_pos, -delta)
  end
end
block_end_align_target(node) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 95
def block_end_align_target(node)
  lineage = [node, *node.ancestors]

  lineage.each_cons(2) do |current, parent|
    return current if end_align_target?(current, parent)
  end

  lineage.last
end
check_block_alignment(start_node, block_node) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 113
def check_block_alignment(start_node, block_node)
  end_loc = block_node.loc.end
  return unless begins_its_line?(end_loc)

  start_loc = start_node.source_range
  return unless start_loc.column != end_loc.column || style == :start_of_block

  do_source_line_column = compute_do_source_line_column(block_node, end_loc)
  return unless do_source_line_column

  register_offense(block_node, start_loc, end_loc, do_source_line_column)
end
compute_do_source_line_column(node, end_loc) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 196
def compute_do_source_line_column(node, end_loc)
  do_loc = node.loc.begin # Actually it's either do or {.

  # We've found that "end" is not aligned with the start node (which
  # can be a block, a variable assignment, etc). But we also allow
  # the "end" to be aligned with the start of the line where the "do"
  # is, which is a style some people use in multi-line chains of
  # blocks.
  match = /\S.*/.match(do_loc.source_line)
  indentation_of_do_line = match.begin(0)
  return unless end_loc.column != indentation_of_do_line || style == :start_of_line

  {
    source: match[0],
    line: do_loc.line,
    column: indentation_of_do_line
  }
end
compute_start_col(ancestor_node, node) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 238
def compute_start_col(ancestor_node, node)
  if style == :start_of_block
    do_loc = node.loc.begin
    return do_loc.source_line =~ /\S/
  end
  (ancestor_node || node).source_range.column
end
disqualified_parent?(parent, node) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 109
def disqualified_parent?(parent, node)
  parent&.loc && parent.first_line != node.first_line && !parent.masgn_type?
end
end_align_target?(node, parent) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 105
def end_align_target?(node, parent)
  disqualified_parent?(parent, node) || !block_end_align_target?(parent, node)
end
find_lhs_node(node) click to toggle source

In offense message, we want to show the assignment LHS rather than the entire assignment.

# File lib/rubocop/cop/layout/block_alignment.rb, line 191
def find_lhs_node(node)
  node, = *node while node.op_asgn_type? || node.masgn_type?
  node
end
format_message(start_loc, end_loc, do_source_line_column, error_source_line_column) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 162
def format_message(start_loc, end_loc, do_source_line_column,
                   error_source_line_column)
  format(
    MSG,
    current: format_source_line_column(loc_to_source_line_column(end_loc)),
    prefer: format_source_line_column(error_source_line_column),
    alt_prefer: alt_start_msg(start_loc, do_source_line_column)
  )
end
format_source_line_column(source_line_column) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 233
def format_source_line_column(source_line_column)
  "`#{source_line_column[:source]}` at #{source_line_column[:line]}, " \
    "#{source_line_column[:column]}"
end
loc_to_source_line_column(loc) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 215
def loc_to_source_line_column(loc)
  {
    source: loc.source.lines.to_a.first.chomp,
    line: loc.line,
    column: loc.column
  }
end
register_offense(block_node, start_loc, end_loc, do_source_line_column) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 126
def register_offense(block_node,
                     start_loc,
                     end_loc,
                     do_source_line_column)

  error_source_line_column = if style == :start_of_block
                               do_source_line_column
                             else
                               loc_to_source_line_column(start_loc)
                             end

  message = format_message(start_loc, end_loc, do_source_line_column,
                           error_source_line_column)

  add_offense(end_loc, message: message) do |corrector|
    autocorrect(corrector, block_node)
  end
end
remove_space_before(corrector, end_pos, delta) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 250
def remove_space_before(corrector, end_pos, delta)
  range = range_between(end_pos - delta, end_pos)

  corrector.remove(range)
end
start_for_block_node(block_node) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 172
def start_for_block_node(block_node)
  # Which node should we align the 'end' with?
  start_node = block_end_align_target(block_node)

  find_lhs_node(start_node)
end
start_for_line_node(block_node) click to toggle source
# File lib/rubocop/cop/layout/block_alignment.rb, line 179
def start_for_line_node(block_node)
  start_node = start_for_block_node(block_node)

  start_node = start_node.each_ancestor.to_a.reverse.find do |node|
    same_line?(start_node, node)
  end || start_node

  find_lhs_node(start_node)
end