class BlockDelimiterSpacing
A RuboCop cop that enforces consistent spacing before block delimiters.
This cop enforces the following style:
foo {bar}- space when method has no parentheses and is not chainedfoo(1, 2) {bar}- space after closing paren for standalone methodsarray.each{|x| x*2}.reverse- no space for method chains (even with parens)->(foo){foo}- no space for lambdas (stabby lambda syntax)lambda{foo}- no space for lambda keywordproc{foo}- no space for proc keywordProc.new{foo}- no space for Proc.new
Definitions
def lambda_or_proc?(send_node)
Check if the send node is a lambda or proc (any form)
Implementation
def lambda_or_proc?(send_node)
return true if send_node.lambda? # stabby lambda: ->{}
return true if send_node.method_name == :lambda # lambda keyword: lambda{}
return true if send_node.method_name == :proc # proc keyword: proc{}
# Check for Proc.new{}
if send_node.method_name == :new && send_node.receiver&.const_type?
# Check if the receiver is the Proc constant
receiver = send_node.receiver
return true if receiver.const_name == :Proc && receiver.children.first.nil?
end
false
end
def check_no_space_for_lambda(block_node, send_node)
Check that there's no space before the opening brace for lambdas
Implementation
def check_no_space_for_lambda(block_node, send_node)
brace_begin = block_node.loc.begin
# Find the position just before the brace
char_before_pos = brace_begin.begin_pos - 1
return if char_before_pos < 0
char_before = processed_source.buffer.source[char_before_pos]
# If there's no space before the brace, we're good
return unless char_before == " "
# Find the extent of whitespace before the brace
start_pos = char_before_pos
while start_pos > 0 && processed_source.buffer.source[start_pos - 1] =~ /\s/
start_pos -= 1
end
space_range = Parser::Source::Range.new(
processed_source.buffer,
start_pos,
brace_begin.begin_pos
)
add_offense(
space_range,
message: MSG_REMOVE_SPACE_LAMBDA
) do |corrector|
corrector.remove(space_range)
end
end
def part_of_method_chain?(block_node)
Check if the block is part of a method chain (e.g., foo.bar or foo.bar.baz)
Implementation
def part_of_method_chain?(block_node)
send_node = block_node.send_node
parent = block_node.parent
# Check if there's a method call after the block (foo{}.bar)
has_chained_method_after = parent&.send_type? && parent.receiver == block_node
# Check if the block's receiver exists (foo.bar{} or array.map{})
# Any method call with a receiver is part of a chain
has_receiver_before = send_node.receiver
has_chained_method_after || has_receiver_before
end
def has_parentheses?(send_node)
Check if the method call has parentheses
Implementation
def has_parentheses?(send_node)
send_node.parenthesized?
end
def check_space_after_parentheses(block_node, send_node)
Check that there's a space between closing paren and opening brace
Implementation
def check_space_after_parentheses(block_node, send_node)
paren_end = send_node.loc.end
brace_begin = block_node.loc.begin
return unless paren_end && brace_begin
# Get the source between ) and {
space_range = Parser::Source::Range.new(
processed_source.buffer,
paren_end.end_pos,
brace_begin.begin_pos
)
space_between = space_range.source
# Should have exactly one space
return if space_between == " "
if space_between.empty?
add_offense(
brace_begin,
message: MSG_ADD_SPACE
) do |corrector|
corrector.insert_before(brace_begin, " ")
end
elsif space_between.match?(/\A\s+\z/)
# Multiple spaces or tabs - replace with single space
add_offense(
space_range,
message: MSG_ADD_SPACE
) do |corrector|
corrector.replace(space_range, " ")
end
end
end
def check_no_space_before_brace(block_node, send_node)
Check that there's no space before the opening brace
Implementation
def check_no_space_before_brace(block_node, send_node)
brace_begin = block_node.loc.begin
# Find the position just before the brace
char_before_pos = brace_begin.begin_pos - 1
return if char_before_pos < 0
char_before = processed_source.buffer.source[char_before_pos]
# If there's a space before the brace, we need to remove it
return unless char_before == " "
# Don't remove space if it's after a closing paren (that case is handled separately)
if send_node.loc.end && send_node.loc.end.end_pos == char_before_pos + 1
return
end
# Find the extent of whitespace before the brace
start_pos = char_before_pos
while start_pos > 0 && processed_source.buffer.source[start_pos - 1] =~ /\s/
start_pos -= 1
end
space_range = Parser::Source::Range.new(
processed_source.buffer,
start_pos,
brace_begin.begin_pos
)
add_offense(
space_range,
message: MSG_REMOVE_SPACE
) do |corrector|
corrector.remove(space_range)
end
end
def check_space_before_brace(block_node, send_node)
Check that there's a space before the opening brace (for standalone methods)
Implementation
def check_space_before_brace(block_node, send_node)
brace_begin = block_node.loc.begin
# Find the position just before the brace
char_before_pos = brace_begin.begin_pos - 1
return if char_before_pos < 0
char_before = processed_source.buffer.source[char_before_pos]
# If there's already a space, we're good
return if char_before == " "
# Otherwise, we need to add a space
add_offense(
brace_begin,
message: MSG_ADD_SPACE
) do |corrector|
corrector.insert_before(brace_begin, " ")
end
end