Skip to content

Support itblock #2077

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Change `RSpec/ScatteredSetup` to allow `around` hooks to be scattered. ([@ydah])
- Fix an error `RSpec/ChangeByZero` cop when without expect block. ([@lee266])
- Fix a false positive for `RSpec/DescribedClass` when `SkipBlocks` is true and numblocks are used. ([@earlopain])
- Add support for `itblock` from Ruby 3.4. ([@earlopain])

## 3.5.0 (2025-02-16)

Expand Down
34 changes: 17 additions & 17 deletions lib/rubocop/cop/rspec/around_block.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ class AroundBlock < Base
(numblock (send nil? :around sym ?) ...)
PATTERN

# @!method hook_itblock(node)
def_node_matcher :hook_itblock, <<~PATTERN
(itblock (send nil? :around sym ?) ...)
PATTERN

# @!method find_arg_usage(node)
def_node_search :find_arg_usage, <<~PATTERN
{(send $... {:call :run}) (send _ _ $...) (yield $...) (block-pass $...)}
Expand All @@ -51,14 +56,20 @@ def on_block(node)
if example_proxy.nil?
add_no_arg_offense(node)
else
check_for_unused_proxy(node, example_proxy)
check_for_unused_proxy(node, example_proxy, example_proxy.name)
end
end
end

def on_numblock(node)
hook_numblock(node) do
check_for_numblock(node)
check_for_unused_proxy(node, node.children.last, :_1)
end
end

def on_itblock(node)
hook_itblock(node) do
check_for_unused_proxy(node, node.children.last, :it)
end
end

Expand All @@ -68,25 +79,14 @@ def add_no_arg_offense(node)
add_offense(node, message: MSG_NO_ARG)
end

def check_for_unused_proxy(block, proxy)
find_arg_usage(block) do |usage|
return if usage.include?(s(:lvar, proxy.name))
end

add_offense(
proxy,
message: format(MSG_UNUSED_ARG, arg: proxy.name)
)
end

def check_for_numblock(block)
def check_for_unused_proxy(block, range, name)
find_arg_usage(block) do |usage|
return if usage.include?(s(:lvar, :_1))
return if usage.include?(s(:lvar, name))
end

add_offense(
block.children.last,
message: format(MSG_UNUSED_ARG, arg: :_1)
range,
message: format(MSG_UNUSED_ARG, arg: name)
)
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec/empty_line_after_hook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def on_block(node)
end

alias on_numblock on_block
alias on_itblock on_block

private

Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec/expect_in_hook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ def on_block(node)
end

alias on_numblock on_block
alias on_itblock on_block

private

Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec/hook_argument.rb
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def on_block(node)
end

alias on_numblock on_block
alias on_itblock on_block

private

Expand Down
4 changes: 1 addition & 3 deletions lib/rubocop/cop/rspec/hooks_before_examples.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,12 @@ class HooksBeforeExamples < Base
}
PATTERN

def on_block(node)
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
return unless example_group_with_body?(node)

check_hooks(node.body) if multiline_block?(node.body)
end

alias on_numblock on_block
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This had no effect, so I removed it. It relies on a previous block and then simply uses next_sibling together with any_block (which includes itblock). I only added tests to show it already works.


private

def multiline_block?(block)
Expand Down
27 changes: 21 additions & 6 deletions lib/rubocop/cop/rspec/iterated_expectation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,44 @@ class IteratedExpectation < Base
)
PATTERN

# @!method each_itblock?(node)
def_node_matcher :each_itblock?, <<~PATTERN
(itblock
(send ... :each) _ $(...)
)
PATTERN

# @!method expectation?(node)
def_node_matcher :expectation?, <<~PATTERN
(send (send nil? :expect (lvar %)) :to ...)
PATTERN

def on_block(node)
each?(node) do |arg, body|
if single_expectation?(body, arg) || only_expectations?(body, arg)
add_offense(node.send_node)
end
check_expectation(node, body, arg)
end
end

def on_numblock(node)
each_numblock?(node) do |body|
if single_expectation?(body, :_1) || only_expectations?(body, :_1)
add_offense(node.send_node)
end
check_expectation(node, body, :_1)
end
end

def on_itblock(node)
each_itblock?(node) do |body|
check_expectation(node, body, :it)
end
end

private

def check_expectation(node, body, arg)
if single_expectation?(body, arg) || only_expectations?(body, arg)
add_offense(node.send_node)
end
end

def single_expectation?(body, arg)
expectation?(body, arg)
end
Expand Down
3 changes: 1 addition & 2 deletions lib/rubocop/cop/rspec/mixin/metadata.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ module Metadata
(send (lvar %) #Hooks.all _ $...)
PATTERN

def on_block(node)
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
rspec_configure(node) do |block_var|
metadata_in_block(node, block_var) do |metadata_arguments|
on_metadata_arguments(metadata_arguments)
Expand All @@ -38,7 +38,6 @@ def on_block(node)
on_metadata_arguments(metadata_arguments)
end
end
alias on_numblock on_block
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module is included in 4 cops and none have tests for numblocks. So I simply removed this one.

I don't think handling these blocks makes sense in context anyways and it was only added here because of the internal affairs cop


def on_metadata(_symbols, _hash)
raise ::NotImplementedError
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec/no_expectation_example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def on_block(node)
end

alias on_numblock on_block
alias on_itblock on_block
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec/redundant_around.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def on_block(node)
end
end
alias on_numblock on_block
alias on_itblock on_block

def on_send(node)
return unless match_redundant_around_hook_send?(node)
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec/skip_block_inside_example.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def on_block(node)
end

alias on_numblock on_block
alias on_itblock on_block

private

Expand Down
2 changes: 1 addition & 1 deletion rubocop-rspec.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ Gem::Specification.new do |spec|
}

spec.add_dependency 'lint_roller', '~> 1.1'
spec.add_dependency 'rubocop', '~> 1.72', '>= 1.72.1'
spec.add_dependency 'rubocop', '~> 1.75'
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.75 transitively depends on rubocop-ast >= 1.43.0 which is good enough

end
62 changes: 62 additions & 0 deletions spec/rubocop/cop/rspec/around_block_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,66 @@
end
end
end

context 'when Ruby 3.4', :ruby34, unsupported_on: :parser do
context 'when the yielded value is unused' do
it 'registers an offense' do
expect_offense(<<~RUBY)
around { it }
^^ You should call `it.call` or `it.run`.
RUBY
end
end

context 'when a method other than #run or #call is called' do
it 'registers an offense' do
expect_offense(<<~RUBY)
around do
it.inspect
^^^^^^^^^^ You should call `it.call` or `it.run`.
end
RUBY
end
end

context 'when #run is called' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
around do
it.run
end
RUBY
end
end

context 'when #call is called' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
around do
it.call
end
RUBY
end
end

context 'when used as a block arg' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
around do
1.times(&it)
end
RUBY
end
end

context 'when passed to another method' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
around do
something_that_might_run_test(it, another_arg)
end
RUBY
end
end
end
end
32 changes: 32 additions & 0 deletions spec/rubocop/cop/rspec/empty_line_after_hook_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -483,5 +483,37 @@
RUBY
end
end

context 'when Ruby 3.4', :ruby34 do
it 'registers an offense for empty line after `around` hook' do
expect_offense(<<~RUBY)
RSpec.describe User do
around { it.run }
^^^^^^^^^^^^^^^^^ Add an empty line after `around`.
it { does_something }
end
RUBY

expect_correction(<<~RUBY)
RSpec.describe User do
around { it.run }

it { does_something }
end
RUBY
end

it 'does not register an offense for multiline `around` block' do
expect_no_offenses(<<~RUBY)
RSpec.describe User do
around do
it.run
end

it { does_something }
end
RUBY
end
end
end
end
16 changes: 16 additions & 0 deletions spec/rubocop/cop/rspec/expect_in_hook_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,20 @@
RUBY
end
end

context 'when Ruby 3.4', :ruby34 do
it 'adds an offense for `expect` in `around` hook' do
expect_offense(<<~RUBY)
around do
expect(something).to eq('foo')
^^^^^^ Do not use `expect` in `around` hook
is_expected(something).to eq('foo')
^^^^^^^^^^^ Do not use `is_expected` in `around` hook
expect_any_instance_of(Something).to receive(:foo)
^^^^^^^^^^^^^^^^^^^^^^ Do not use `expect_any_instance_of` in `around` hook
it.run
end
RUBY
end
end
end
Loading
Loading