Skip to content

Commit e4a4818

Browse files
committed
Support itblock
All cops that handle numblock now also handle itblock. `itblock` is relatively new and the required `rubocop-ast` version is required via RuboCop 1.75
1 parent 170cc32 commit e4a4818

22 files changed

+389
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Change `RSpec/ScatteredSetup` to allow `around` hooks to be scattered. ([@ydah])
99
- Fix an error `RSpec/ChangeByZero` cop when without expect block. ([@lee266])
1010
- Fix a false positive for `RSpec/DescribedClass` when `SkipBlocks` is true and numblocks are used. ([@earlopain])
11+
- Add support for `itblock` from Ruby 3.4. ([@earlopain])
1112

1213
## 3.5.0 (2025-02-16)
1314

lib/rubocop/cop/rspec/around_block.rb

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,11 @@ class AroundBlock < Base
4141
(numblock (send nil? :around sym ?) ...)
4242
PATTERN
4343

44+
# @!method hook_itblock(node)
45+
def_node_matcher :hook_itblock, <<~PATTERN
46+
(itblock (send nil? :around sym ?) ...)
47+
PATTERN
48+
4449
# @!method find_arg_usage(node)
4550
def_node_search :find_arg_usage, <<~PATTERN
4651
{(send $... {:call :run}) (send _ _ $...) (yield $...) (block-pass $...)}
@@ -51,14 +56,20 @@ def on_block(node)
5156
if example_proxy.nil?
5257
add_no_arg_offense(node)
5358
else
54-
check_for_unused_proxy(node, example_proxy)
59+
check_for_unused_proxy(node, example_proxy, example_proxy.name)
5560
end
5661
end
5762
end
5863

5964
def on_numblock(node)
6065
hook_numblock(node) do
61-
check_for_numblock(node)
66+
check_for_unused_proxy(node, node.children.last, :_1)
67+
end
68+
end
69+
70+
def on_itblock(node)
71+
hook_itblock(node) do
72+
check_for_unused_proxy(node, node.children.last, :it)
6273
end
6374
end
6475

@@ -68,25 +79,14 @@ def add_no_arg_offense(node)
6879
add_offense(node, message: MSG_NO_ARG)
6980
end
7081

71-
def check_for_unused_proxy(block, proxy)
72-
find_arg_usage(block) do |usage|
73-
return if usage.include?(s(:lvar, proxy.name))
74-
end
75-
76-
add_offense(
77-
proxy,
78-
message: format(MSG_UNUSED_ARG, arg: proxy.name)
79-
)
80-
end
81-
82-
def check_for_numblock(block)
82+
def check_for_unused_proxy(block, range, name)
8383
find_arg_usage(block) do |usage|
84-
return if usage.include?(s(:lvar, :_1))
84+
return if usage.include?(s(:lvar, name))
8585
end
8686

8787
add_offense(
88-
block.children.last,
89-
message: format(MSG_UNUSED_ARG, arg: :_1)
88+
range,
89+
message: format(MSG_UNUSED_ARG, arg: name)
9090
)
9191
end
9292
end

lib/rubocop/cop/rspec/empty_line_after_hook.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def on_block(node)
6868
end
6969

7070
alias on_numblock on_block
71+
alias on_itblock on_block
7172

7273
private
7374

lib/rubocop/cop/rspec/expect_in_hook.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def on_block(node)
3838
end
3939

4040
alias on_numblock on_block
41+
alias on_itblock on_block
4142

4243
private
4344

lib/rubocop/cop/rspec/hook_argument.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ def on_block(node)
8989
end
9090

9191
alias on_numblock on_block
92+
alias on_itblock on_block
9293

9394
private
9495

lib/rubocop/cop/rspec/hooks_before_examples.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,12 @@ class HooksBeforeExamples < Base
3838
}
3939
PATTERN
4040

41-
def on_block(node)
41+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
4242
return unless example_group_with_body?(node)
4343

4444
check_hooks(node.body) if multiline_block?(node.body)
4545
end
4646

47-
alias on_numblock on_block
48-
4947
private
5048

5149
def multiline_block?(block)

lib/rubocop/cop/rspec/iterated_expectation.rb

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,44 @@ class IteratedExpectation < Base
3636
)
3737
PATTERN
3838

39+
# @!method each_itblock?(node)
40+
def_node_matcher :each_itblock?, <<~PATTERN
41+
(itblock
42+
(send ... :each) _ $(...)
43+
)
44+
PATTERN
45+
3946
# @!method expectation?(node)
4047
def_node_matcher :expectation?, <<~PATTERN
4148
(send (send nil? :expect (lvar %)) :to ...)
4249
PATTERN
4350

4451
def on_block(node)
4552
each?(node) do |arg, body|
46-
if single_expectation?(body, arg) || only_expectations?(body, arg)
47-
add_offense(node.send_node)
48-
end
53+
check_expectation(node, body, arg)
4954
end
5055
end
5156

5257
def on_numblock(node)
5358
each_numblock?(node) do |body|
54-
if single_expectation?(body, :_1) || only_expectations?(body, :_1)
55-
add_offense(node.send_node)
56-
end
59+
check_expectation(node, body, :_1)
60+
end
61+
end
62+
63+
def on_itblock(node)
64+
each_itblock?(node) do |body|
65+
check_expectation(node, body, :it)
5766
end
5867
end
5968

6069
private
6170

71+
def check_expectation(node, body, arg)
72+
if single_expectation?(body, arg) || only_expectations?(body, arg)
73+
add_offense(node.send_node)
74+
end
75+
end
76+
6277
def single_expectation?(body, arg)
6378
expectation?(body, arg)
6479
end

lib/rubocop/cop/rspec/mixin/metadata.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module Metadata
2727
(send (lvar %) #Hooks.all _ $...)
2828
PATTERN
2929

30-
def on_block(node)
30+
def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
3131
rspec_configure(node) do |block_var|
3232
metadata_in_block(node, block_var) do |metadata_arguments|
3333
on_metadata_arguments(metadata_arguments)
@@ -38,7 +38,6 @@ def on_block(node)
3838
on_metadata_arguments(metadata_arguments)
3939
end
4040
end
41-
alias on_numblock on_block
4241

4342
def on_metadata(_symbols, _hash)
4443
raise ::NotImplementedError

lib/rubocop/cop/rspec/no_expectation_example.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def on_block(node)
9696
end
9797

9898
alias on_numblock on_block
99+
alias on_itblock on_block
99100
end
100101
end
101102
end

lib/rubocop/cop/rspec/redundant_around.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def on_block(node)
2828
end
2929
end
3030
alias on_numblock on_block
31+
alias on_itblock on_block
3132

3233
def on_send(node)
3334
return unless match_redundant_around_hook_send?(node)

lib/rubocop/cop/rspec/skip_block_inside_example.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def on_block(node)
3434
end
3535

3636
alias on_numblock on_block
37+
alias on_itblock on_block
3738

3839
private
3940

rubocop-rspec.gemspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ Gem::Specification.new do |spec|
3939
}
4040

4141
spec.add_dependency 'lint_roller', '~> 1.1'
42-
spec.add_dependency 'rubocop', '~> 1.72', '>= 1.72.1'
42+
spec.add_dependency 'rubocop', '~> 1.75'
4343
end

spec/rubocop/cop/rspec/around_block_spec.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,66 @@
178178
end
179179
end
180180
end
181+
182+
context 'when Ruby 3.4', :ruby34, unsupported_on: :parser do
183+
context 'when the yielded value is unused' do
184+
it 'registers an offense' do
185+
expect_offense(<<~RUBY)
186+
around { it }
187+
^^ You should call `it.call` or `it.run`.
188+
RUBY
189+
end
190+
end
191+
192+
context 'when a method other than #run or #call is called' do
193+
it 'registers an offense' do
194+
expect_offense(<<~RUBY)
195+
around do
196+
it.inspect
197+
^^^^^^^^^^ You should call `it.call` or `it.run`.
198+
end
199+
RUBY
200+
end
201+
end
202+
203+
context 'when #run is called' do
204+
it 'does not register an offense' do
205+
expect_no_offenses(<<~RUBY)
206+
around do
207+
it.run
208+
end
209+
RUBY
210+
end
211+
end
212+
213+
context 'when #call is called' do
214+
it 'does not register an offense' do
215+
expect_no_offenses(<<~RUBY)
216+
around do
217+
it.call
218+
end
219+
RUBY
220+
end
221+
end
222+
223+
context 'when used as a block arg' do
224+
it 'does not register an offense' do
225+
expect_no_offenses(<<~RUBY)
226+
around do
227+
1.times(&it)
228+
end
229+
RUBY
230+
end
231+
end
232+
233+
context 'when passed to another method' do
234+
it 'does not register an offense' do
235+
expect_no_offenses(<<~RUBY)
236+
around do
237+
something_that_might_run_test(it, another_arg)
238+
end
239+
RUBY
240+
end
241+
end
242+
end
181243
end

spec/rubocop/cop/rspec/empty_line_after_hook_spec.rb

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,5 +483,37 @@
483483
RUBY
484484
end
485485
end
486+
487+
context 'when Ruby 3.4', :ruby34, unsupported_on: :parser do
488+
it 'registers an offense for empty line after `around` hook' do
489+
expect_offense(<<~RUBY)
490+
RSpec.describe User do
491+
around { it.run }
492+
^^^^^^^^^^^^^^^^^ Add an empty line after `around`.
493+
it { does_something }
494+
end
495+
RUBY
496+
497+
expect_correction(<<~RUBY)
498+
RSpec.describe User do
499+
around { it.run }
500+
501+
it { does_something }
502+
end
503+
RUBY
504+
end
505+
506+
it 'does not register an offense for multiline `around` block' do
507+
expect_no_offenses(<<~RUBY)
508+
RSpec.describe User do
509+
around do
510+
it.run
511+
end
512+
513+
it { does_something }
514+
end
515+
RUBY
516+
end
517+
end
486518
end
487519
end

spec/rubocop/cop/rspec/expect_in_hook_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,20 @@
9090
RUBY
9191
end
9292
end
93+
94+
context 'when Ruby 3.4', :ruby34, unsupported_on: :parser do
95+
it 'adds an offense for `expect` in `around` hook' do
96+
expect_offense(<<~RUBY)
97+
around do
98+
expect(something).to eq('foo')
99+
^^^^^^ Do not use `expect` in `around` hook
100+
is_expected(something).to eq('foo')
101+
^^^^^^^^^^^ Do not use `is_expected` in `around` hook
102+
expect_any_instance_of(Something).to receive(:foo)
103+
^^^^^^^^^^^^^^^^^^^^^^ Do not use `expect_any_instance_of` in `around` hook
104+
it.run
105+
end
106+
RUBY
107+
end
108+
end
93109
end

spec/rubocop/cop/rspec/hook_argument_spec.rb

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,5 +288,35 @@
288288
RUBY
289289
end
290290
end
291+
292+
context 'when Ruby 3.4', :ruby34, unsupported_on: :parser do
293+
it 'does not flag :example for hooks' do
294+
expect_no_offenses(<<~RUBY)
295+
around(:example) { it }
296+
RUBY
297+
end
298+
299+
it 'detects :each for hooks' do
300+
expect_offense(<<~RUBY)
301+
around(:each) { it }
302+
^^^^^^^^^^^^^ Use `:example` for RSpec hooks.
303+
RUBY
304+
305+
expect_correction(<<~RUBY)
306+
around(:example) { it }
307+
RUBY
308+
end
309+
310+
it 'detects hooks without default scopes' do
311+
expect_offense(<<~RUBY)
312+
around { it }
313+
^^^^^^ Use `:example` for RSpec hooks.
314+
RUBY
315+
316+
expect_correction(<<~RUBY)
317+
around(:example) { it }
318+
RUBY
319+
end
320+
end
291321
end
292322
end

0 commit comments

Comments
 (0)