Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new
Style/ExplicitBlockArgument
cop
- Loading branch information
Showing
9 changed files
with
267 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module Style | ||
# This cop enforces the use of explicit block argument to avoid writing | ||
# block literal that just passes its arguments to another block. | ||
# | ||
# @example | ||
# # bad | ||
# def with_tmp_dir | ||
# Dir.mktmpdir do |tmp_dir| | ||
# Dir.chdir(tmp_dir) { |dir| yield dir } # block just passes arguments | ||
# end | ||
# end | ||
# | ||
# # good | ||
# def with_tmp_dir(&block) | ||
# Dir.mktmpdir do |tmp_dir| | ||
# Dir.chdir(tmp_dir, &block) | ||
# end | ||
# end | ||
# | ||
# with_tmp_dir do |dir| | ||
# puts "dir is accessible as a parameter and pwd is set: #{dir}" | ||
# end | ||
# | ||
class ExplicitBlockArgument < Base | ||
include RangeHelp | ||
extend AutoCorrector | ||
|
||
MSG = 'Consider using explicit block argument in the '\ | ||
"surrounding method's signature over `yield`." | ||
|
||
def_node_matcher :yielding_block?, <<~PATTERN | ||
(block $_ (args $...) (yield $...)) | ||
PATTERN | ||
|
||
def initialize(config = nil, options = nil) | ||
super | ||
@def_nodes = Set.new | ||
end | ||
|
||
def on_yield(node) | ||
block_node = node.parent | ||
|
||
yielding_block?(block_node) do |send_node, block_args, yield_args| | ||
return unless yielding_arguments?(block_args, yield_args) | ||
|
||
add_offense(block_node) do |corrector| | ||
corrector.remove(block_body_range(block_node, send_node)) | ||
|
||
add_block_argument(send_node, corrector) | ||
|
||
def_node = block_node.each_ancestor(:def, :defs).first | ||
add_block_argument(def_node, corrector) if @def_nodes.add?(def_node) | ||
end | ||
end | ||
end | ||
|
||
private | ||
|
||
def yielding_arguments?(block_args, yield_args) | ||
return false if yield_args.empty? | ||
|
||
yield_args.zip(block_args).all? do |yield_arg, block_arg| | ||
block_arg && yield_arg.children.first == block_arg.children.first | ||
end | ||
end | ||
|
||
def add_block_argument(node, corrector) | ||
if node.arguments? | ||
last_arg = node.arguments.last | ||
corrector.insert_after(last_arg, ', &block') unless last_arg.blockarg_type? | ||
elsif node.send_type? | ||
corrector.insert_after(node, '(&block)') | ||
else | ||
corrector.insert_after(node.loc.name, '(&block)') | ||
end | ||
end | ||
|
||
def block_body_range(block_node, send_node) | ||
range_between( | ||
send_node.loc.expression.end_pos, | ||
block_node.loc.end.end_pos | ||
) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::Style::ExplicitBlockArgument do | ||
subject(:cop) { described_class.new } | ||
|
||
it 'registers an offense and corrects when block just yields its arguments' do | ||
expect_offense(<<~RUBY) | ||
def m | ||
items.something(first_arg) { |i| yield i } | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Consider using explicit block argument in the surrounding method's signature over `yield`. | ||
end | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
def m(&block) | ||
items.something(first_arg, &block) | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when block yields several first its arguments' do | ||
expect_offense(<<~RUBY) | ||
def m | ||
items.something { |i, j| yield i } | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Consider using explicit block argument in the surrounding method's signature over `yield`. | ||
end | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
def m(&block) | ||
items.something(&block) | ||
end | ||
RUBY | ||
end | ||
|
||
it 'correctly corrects when method already has an explicit block argument' do | ||
expect_offense(<<~RUBY) | ||
def m(&block) | ||
items.something { |i| yield i } | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Consider using explicit block argument in the surrounding method's signature over `yield`. | ||
end | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
def m(&block) | ||
items.something(&block) | ||
end | ||
RUBY | ||
end | ||
|
||
it 'registers an offense and corrects when method contains multiple `yield`s' do | ||
expect_offense(<<~RUBY) | ||
def m | ||
items.something { |i| yield i } | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Consider using explicit block argument in the surrounding method's signature over `yield`. | ||
if condition | ||
yield 2 | ||
elsif other_condition | ||
3.times { yield } | ||
else | ||
other_items.something { |i, j| yield i } | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Consider using explicit block argument in the surrounding method's signature over `yield`. | ||
end | ||
end | ||
RUBY | ||
|
||
expect_correction(<<~RUBY) | ||
def m(&block) | ||
items.something(&block) | ||
if condition | ||
yield 2 | ||
elsif other_condition | ||
3.times { yield } | ||
else | ||
other_items.something(&block) | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when `yield` is not inside block' do | ||
expect_no_offenses(<<~RUBY) | ||
def m | ||
yield i | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when `yield` inside block has no arguments' do | ||
expect_no_offenses(<<~RUBY) | ||
def m | ||
3.times { yield } | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when `yield` is the sole block body' do | ||
expect_no_offenses(<<~RUBY) | ||
def m | ||
items.something do |i| | ||
do_something | ||
yield i | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'does not register an offense when `yield` arguments is not a prefix of block arguments' do | ||
expect_no_offenses(<<~RUBY) | ||
def m | ||
items.something { |i, j, k| yield j, k } | ||
end | ||
RUBY | ||
end | ||
end |