Skip to content

Commit

Permalink
Add new Style/SingleLineDoEndBlock cop
Browse files Browse the repository at this point in the history
This PR adds new `Style/SingleLineDoEndBlock` cop
that checks for single-line `do`...`end` blocks.

```ruby
# bad
foo do |arg| bar(arg) end

# good
foo do |arg|
  bar(arg)
end

# bad
->(arg) do bar(arg) end

# good
->(arg) { bar(arg) }
```

In practice a single line `do`...`end` is autocorrected when `EnforcedStyle: semantic` in `Style/BlockDelimiters`.
It can also be detected by this cop if it is written by handcraft.
So I decided to introduce this new cop instead of an autocorrect extension of `Style/BlockDelimiters`.
  • Loading branch information
koic committed Sep 29, 2023
1 parent 91d3a81 commit 8ad7335
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 1 deletion.
1 change: 1 addition & 0 deletions changelog/new_add_new_style_single_line_do_end_block.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#12227](https://github.com/rubocop/rubocop/pull/12227): Add new `Style/SingleLineDoEndBlock` cop. ([@koic][])
5 changes: 5 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5186,6 +5186,11 @@ Style/SingleLineBlockParams:
- acc
- elem

Style/SingleLineDoEndBlock:
Description: 'Checks for single-line `do`...`end` blocks.'
Enabled: pending
VersionAdded: '<<next>>'

Style/SingleLineMethods:
Description: 'Avoid single-line methods.'
StyleGuide: '#no-single-line-methods'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,7 @@
require_relative 'rubocop/cop/style/redundant_self_assignment'
require_relative 'rubocop/cop/style/redundant_self_assignment_branch'
require_relative 'rubocop/cop/style/require_order'
require_relative 'rubocop/cop/style/single_line_do_end_block'
require_relative 'rubocop/cop/style/sole_nested_conditional'
require_relative 'rubocop/cop/style/static_class'
require_relative 'rubocop/cop/style/map_compact_with_conditional_block'
Expand Down
65 changes: 65 additions & 0 deletions lib/rubocop/cop/style/single_line_do_end_block.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# Checks for single-line `do`...`end` block.
#
# In practice a single line `do`...`end` is autocorrected when `EnforcedStyle: semantic`
# in `Style/BlockDelimiters`. The autocorrection maintains the `do` ... `end` syntax to
# preserve semantics and does not change it to `{`...`}` block.
#
# @example
#
# # bad
# foo do |arg| bar(arg) end
#
# # good
# foo do |arg|
# bar(arg)
# end
#
# # bad
# ->(arg) do bar(arg) end
#
# # good
# ->(arg) { bar(arg) }
#
class SingleLineDoEndBlock < Base
extend AutoCorrector

MSG = 'Prefer multiline `do`...`end` block.'

def on_block(node)
return if !node.single_line? || node.braces?

add_offense(node) do |corrector|
corrector.insert_after(do_line(node), "\n")

node_body = node.body

if node_body.respond_to?(:heredoc?) && node_body.heredoc?
corrector.remove(node.loc.end)
corrector.insert_after(node_body.loc.heredoc_end, "\nend")
else
corrector.insert_after(node_body, "\n")
end
end
end
alias on_numblock on_block

private

def do_line(node)
if node.numblock_type? || node.arguments.children.empty? || node.send_node.lambda_literal?
node.loc.begin
else
node.arguments
end
end

def x(corrector, node); end
end
end
end
end
99 changes: 99 additions & 0 deletions spec/rubocop/cop/style/single_line_do_end_block_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Style::SingleLineDoEndBlock, :config do
it 'registers an offense when using single line `do`...`end`' do
expect_offense(<<~RUBY)
foo do bar end
^^^^^^^^^^^^^^ Prefer multiline `do`...`end` block.
RUBY

expect_correction(<<~RUBY)
foo do
bar
end
RUBY
end

it 'registers an offense when using single line `do`...`end` with block argument' do
expect_offense(<<~RUBY)
foo do |arg| bar(arg) end
^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer multiline `do`...`end` block.
RUBY

expect_correction(<<~RUBY)
foo do |arg|
bar(arg)
end
RUBY
end

it 'registers an offense when using single line `do`...`end` with numbered block argument' do
expect_offense(<<~RUBY)
foo do bar(_1) end
^^^^^^^^^^^^^^^^^^ Prefer multiline `do`...`end` block.
RUBY

expect_correction(<<~RUBY)
foo do
bar(_1)
end
RUBY
end

it 'registers an offense when using single line `do`...`end` with heredoc body' do
expect_offense(<<~RUBY)
foo do <<~EOS end
^^^^^^^^^^^^^^^^^ Prefer multiline `do`...`end` block.
text
EOS
RUBY

expect_correction(<<~RUBY)
foo do
<<~EOS#{' '}
text
EOS
end
RUBY
end

it 'registers an offense when using single line `do`...`end` with `->` block' do
expect_offense(<<~RUBY)
->(arg) do foo arg end
^^^^^^^^^^^^^^^^^^^^^^ Prefer multiline `do`...`end` block.
RUBY

expect_correction(<<~RUBY)
->(arg) do
foo arg
end
RUBY
end

it 'registers an offense when using single line `do`...`end` with `lambda` block' do
expect_offense(<<~RUBY)
lambda do |arg| foo(arg) end
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer multiline `do`...`end` block.
RUBY

expect_correction(<<~RUBY)
lambda do |arg|
foo(arg)
end
RUBY
end

it 'does not register an offense when using multiline `do`...`end`' do
expect_no_offenses(<<~RUBY)
foo do
bar
end
RUBY
end

it 'does not register an offense when using single line `{`...`}`' do
expect_no_offenses(<<~RUBY)
foo { bar }
RUBY
end
end
2 changes: 1 addition & 1 deletion spec/rubocop/cop/team_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ def a

let(:file_path) { 'Gemfile' }

let(:buggy_correction) { ->(_corrector) do raise cause end }
let(:buggy_correction) { ->(_corrector) { raise cause } }
let(:options) { { autocorrect: true } }

let(:cause) { StandardError.new('cause') }
Expand Down

0 comments on commit 8ad7335

Please sign in to comment.