@@ -11,19 +11,21 @@ module Layout
1111 # A RuboCop cop that enforces consistent spacing before block delimiters.
1212 #
1313 # This cop enforces the following style:
14- # - `foo {bar}` - space when method has no parentheses and is not chained
15- # - `foo(1, 2) {bar}` - space after closing paren for standalone methods
16- # - `array.each{|x| x*2}.reverse` - no space for method chains (even with parens)
17- # - `->(foo){foo}` - no space for lambdas (stabby lambda syntax)
18- # - `lambda{foo}` - no space for lambda keyword
19- # - `proc{foo}` - no space for proc keyword
20- # - `Proc.new{foo}` - no space for Proc.new
14+ # - `foo {bar}` - space for top-level statements without parentheses.
15+ # - `x = foo{bar}` - no space when part of an expression (assignment, argument, etc).
16+ # - `foo(1, 2) {bar}` - space after closing paren for top-level statements.
17+ # - `array.each{|x| x*2}.reverse` - no space for method chains.
18+ # - `->(foo){foo}` - no space for lambdas (stabby lambda syntax).
19+ # - `lambda{foo}` - no space for lambda keyword.
20+ # - `proc{foo}` - no space for proc keyword.
21+ # - `Proc.new{foo}` - no space for `Proc.new`.
2122 class BlockDelimiterSpacing < RuboCop ::Cop ::Base
2223 extend Cop ::AutoCorrector
2324
2425 MSG_ADD_SPACE = "Add a space before the opening brace."
2526 MSG_REMOVE_SPACE = "Remove space before the opening brace for method chains."
2627 MSG_REMOVE_SPACE_LAMBDA = "Remove space before the opening brace for lambdas/procs."
28+ MSG_REMOVE_SPACE_EXPRESSION = "Remove space before the opening brace for expressions."
2729
2830 def on_block ( node )
2931 return unless node . braces?
@@ -44,6 +46,12 @@ def on_block(node)
4446 # array.each{|x| x*2}.reverse - no space
4547 # obj.method(1, 2){|x| x}.other - also no space
4648 check_no_space_before_brace ( node , send_node )
49+ # Priority 3: Check if it's part of an expression (not top-level)
50+ # Blocks within expressions should have no space
51+ elsif part_of_expression? ( node )
52+ # x = Async{server.run} - no space (part of assignment)
53+ # foo(bar{baz}) - no space (part of argument)
54+ check_no_space_for_expression ( node , send_node )
4755 elsif has_parentheses? ( send_node )
4856 # foo(1, 2) {bar} - space after ) for standalone methods
4957 check_space_after_parentheses ( node , send_node )
@@ -71,6 +79,18 @@ def lambda_or_proc?(send_node)
7179 false
7280 end
7381
82+ # Check if the block is part of an expression (not a top-level statement)
83+ # Top-level statements are directly inside a :begin node (file/method body)
84+ # and should have space. Everything else (expressions, nested blocks) should not.
85+ def part_of_expression? ( node )
86+ parent = node . parent
87+ return false unless parent
88+
89+ # If parent is a :begin node (sequence of statements), this is top-level
90+ # Otherwise, it's part of an expression or nested context
91+ parent . type != :begin
92+ end
93+
7494 # Check that there's no space before the opening brace for lambdas
7595 def check_no_space_for_lambda ( block_node , send_node )
7696 brace_begin = block_node . loc . begin
@@ -105,6 +125,40 @@ def check_no_space_for_lambda(block_node, send_node)
105125 end
106126 end
107127
128+ # Check that there's no space before the opening brace for expressions
129+ def check_no_space_for_expression ( block_node , send_node )
130+ brace_begin = block_node . loc . begin
131+
132+ # Find the position just before the brace
133+ char_before_pos = brace_begin . begin_pos - 1
134+
135+ return if char_before_pos < 0
136+
137+ char_before = processed_source . buffer . source [ char_before_pos ]
138+
139+ # If there's no space before the brace, we're good
140+ return unless char_before == " "
141+
142+ # Find the extent of whitespace before the brace
143+ start_pos = char_before_pos
144+ while start_pos > 0 && processed_source . buffer . source [ start_pos - 1 ] =~ /\s /
145+ start_pos -= 1
146+ end
147+
148+ space_range = Parser ::Source ::Range . new (
149+ processed_source . buffer ,
150+ start_pos ,
151+ brace_begin . begin_pos
152+ )
153+
154+ add_offense (
155+ space_range ,
156+ message : MSG_REMOVE_SPACE_EXPRESSION
157+ ) do |corrector |
158+ corrector . remove ( space_range )
159+ end
160+ end
161+
108162 # Check if the block is part of a method chain (e.g., foo{}.bar or foo.bar{}.baz)
109163 def part_of_method_chain? ( block_node )
110164 send_node = block_node . send_node
0 commit comments