lib/rubocop/cop/layout/space_inside_block_braces.rb
# frozen_string_literal: true
module RuboCop
module Cop
module Layout
# Checks that block braces have or don't have surrounding space inside
# them on configuration. For blocks taking parameters, it checks that the
# left brace has or doesn't have trailing space depending on
# configuration.
#
# @example EnforcedStyle: space (default)
# # The `space` style enforces that block braces have
# # surrounding space.
#
# # bad
# some_array.each {puts e}
#
# # good
# some_array.each { puts e }
#
# @example EnforcedStyle: no_space
# # The `no_space` style enforces that block braces don't
# # have surrounding space.
#
# # bad
# some_array.each { puts e }
#
# # good
# some_array.each {puts e}
#
#
# @example EnforcedStyleForEmptyBraces: no_space (default)
# # The `no_space` EnforcedStyleForEmptyBraces style enforces that
# # block braces don't have a space in between when empty.
#
# # bad
# some_array.each { }
# some_array.each { }
# some_array.each { }
#
# # good
# some_array.each {}
#
# @example EnforcedStyleForEmptyBraces: space
# # The `space` EnforcedStyleForEmptyBraces style enforces that
# # block braces have at least a space in between when empty.
#
# # bad
# some_array.each {}
#
# # good
# some_array.each { }
# some_array.each { }
# some_array.each { }
#
#
# @example SpaceBeforeBlockParameters: true (default)
# # The SpaceBeforeBlockParameters style set to `true` enforces that
# # there is a space between `{` and `|`. Overrides `EnforcedStyle`
# # if there is a conflict.
#
# # bad
# [1, 2, 3].each {|n| n * 2 }
#
# # good
# [1, 2, 3].each { |n| n * 2 }
#
# @example SpaceBeforeBlockParameters: false
# # The SpaceBeforeBlockParameters style set to `false` enforces that
# # there is no space between `{` and `|`. Overrides `EnforcedStyle`
# # if there is a conflict.
#
# # bad
# [1, 2, 3].each { |n| n * 2 }
#
# # good
# [1, 2, 3].each {|n| n * 2 }
#
class SpaceInsideBlockBraces < Base
include ConfigurableEnforcedStyle
include SurroundingSpace
include RangeHelp
extend AutoCorrector
def on_block(node)
return if node.keywords?
# Do not register an offense for multi-line empty braces. That means
# preventing autocorrection to single-line empty braces. It will
# conflict with autocorrection by `Layout/SpaceInsideBlockBraces` cop
# if autocorrected to a single-line empty braces.
# See: https://github.com/rubocop/rubocop/issues/7363
return if node.body.nil? && node.multiline?
left_brace = node.loc.begin
right_brace = node.loc.end
check_inside(node, left_brace, right_brace)
end
alias on_numblock on_block
private
def check_inside(node, left_brace, right_brace)
if left_brace.end_pos == right_brace.begin_pos
adjacent_braces(left_brace, right_brace)
else
range = range_between(left_brace.end_pos, right_brace.begin_pos)
inner = range.source
if /\S/.match?(inner)
braces_with_contents_inside(node, inner)
elsif style_for_empty_braces == :no_space
offense(range.begin_pos, range.end_pos,
'Space inside empty braces detected.',
'EnforcedStyleForEmptyBraces')
end
end
end
def adjacent_braces(left_brace, right_brace)
return if style_for_empty_braces != :space
offense(left_brace.begin_pos, right_brace.end_pos,
'Space missing inside empty braces.',
'EnforcedStyleForEmptyBraces')
end
def braces_with_contents_inside(node, inner)
args_delimiter = node.arguments.loc.begin if node.block_type? # Can be ( | or nil.
check_left_brace(inner, node.loc.begin, args_delimiter)
check_right_brace(node, inner, node.loc.begin, node.loc.end, node.single_line?)
end
def check_left_brace(inner, left_brace, args_delimiter)
if /\A\S/.match?(inner)
no_space_inside_left_brace(left_brace, args_delimiter)
else
space_inside_left_brace(left_brace, args_delimiter)
end
end
def check_right_brace(node, inner, left_brace, right_brace, single_line)
if single_line && /\S$/.match?(inner)
no_space(right_brace.begin_pos, right_brace.end_pos, 'Space missing inside }.')
else
column = node.source_range.column
return if multiline_block?(left_brace, right_brace) &&
aligned_braces?(inner, right_brace, column)
space_inside_right_brace(inner, right_brace, column)
end
end
def multiline_block?(left_brace, right_brace)
left_brace.first_line != right_brace.first_line
end
def aligned_braces?(inner, right_brace, column)
column == right_brace.column || column == inner_last_space_count(inner)
end
def inner_last_space_count(inner)
inner.split("\n").last.count(' ')
end
def no_space_inside_left_brace(left_brace, args_delimiter)
if pipe?(args_delimiter)
if left_brace.end_pos == args_delimiter.begin_pos &&
cop_config['SpaceBeforeBlockParameters']
offense(left_brace.begin_pos, args_delimiter.end_pos,
'Space between { and | missing.')
else
correct_style_detected
end
else
# We indicate the position after the left brace. Otherwise it's
# difficult to distinguish between space missing to the left and to
# the right of the brace in autocorrect.
no_space(left_brace.end_pos, left_brace.end_pos + 1, 'Space missing inside {.')
end
end
def space_inside_left_brace(left_brace, args_delimiter)
if pipe?(args_delimiter)
if cop_config['SpaceBeforeBlockParameters']
correct_style_detected
else
offense(left_brace.end_pos, args_delimiter.begin_pos,
'Space between { and | detected.')
end
else
brace_with_space = range_with_surrounding_space(left_brace, side: :right)
space(brace_with_space.begin_pos + 1, brace_with_space.end_pos,
'Space inside { detected.')
end
end
def pipe?(args_delimiter)
args_delimiter&.is?('|')
end
def space_inside_right_brace(inner, right_brace, column)
brace_with_space = range_with_surrounding_space(right_brace, side: :left)
begin_pos = brace_with_space.begin_pos
end_pos = brace_with_space.end_pos - 1
if brace_with_space.source.match?(/\R/)
begin_pos = end_pos - (right_brace.column - column)
end
if inner.end_with?(']')
end_pos -= 1
begin_pos = end_pos - (inner_last_space_count(inner) - column)
end
space(begin_pos, end_pos, 'Space inside } detected.')
end
def no_space(begin_pos, end_pos, msg)
if style == :space
offense(begin_pos, end_pos, msg)
else
correct_style_detected
end
end
def space(begin_pos, end_pos, msg)
if style == :no_space
offense(begin_pos, end_pos, msg)
else
correct_style_detected
end
end
def offense(begin_pos, end_pos, msg, style_param = 'EnforcedStyle')
return if begin_pos > end_pos
range = range_between(begin_pos, end_pos)
add_offense(range, message: msg) do |corrector|
case range.source
when /\s/ then corrector.remove(range)
when '{}' then corrector.replace(range, '{ }')
when '{|' then corrector.replace(range, '{ |')
else corrector.insert_before(range, ' ')
end
opposite_style_detected if style_param == 'EnforcedStyle'
end
end
def style_for_empty_braces
case cop_config['EnforcedStyleForEmptyBraces']
when 'space' then :space
when 'no_space' then :no_space
else raise 'Unknown EnforcedStyleForEmptyBraces selected!'
end
end
end
end
end
end