Skip to content

Commit e73d74d

Browse files
committed
Fix false positive in RSpec/LetSetup cop when let! variables are used in blocks declared in an outer context
1 parent 7a73000 commit e73d74d

File tree

3 files changed

+117
-1
lines changed

3 files changed

+117
-1
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Master (Unreleased)
44

5+
- Fix false positive in `RSpec/LetSetup` cop when `let!` variables are used in blocks declared in an outer context. ([@ydah])
6+
57
## 3.8.0 (2025-11-12)
68

79
- Add new cop `RSpec/LeakyLocalVariable`. ([@lovro-bikic])

lib/rubocop/cop/rspec/let_setup.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
7272
def unused_let_bang(node)
7373
child_let_bang(node) do |method_send, method_name|
7474
next if overrides_outer_let_bang?(node, method_name)
75+
next if method_called_in_scope?(node, method_name.to_sym)
7576

76-
yield(method_send) unless method_called?(node, method_name.to_sym)
77+
yield(method_send)
7778
end
7879
end
7980

@@ -98,6 +99,24 @@ def outer_let_bang?(ancestor_node, method_name)
9899
end
99100
end
100101
end
102+
103+
def method_called_in_scope?(node, method_name)
104+
method_called?(node, method_name) ||
105+
method_called_in_parent_hooks?(node, method_name)
106+
end
107+
108+
def method_called_in_parent_hooks?(node, method_name)
109+
node.each_ancestor(:block).any? do |ancestor|
110+
example_or_shared_group_or_including?(ancestor) &&
111+
method_called_in_hooks?(ancestor, method_name)
112+
end
113+
end
114+
115+
def method_called_in_hooks?(node, method_name)
116+
RuboCop::RSpec::ExampleGroup.new(node).hooks.any? do |hook|
117+
method_called?(hook.to_node, method_name)
118+
end
119+
end
101120
end
102121
end
103122
end

spec/rubocop/cop/rspec/let_setup_spec.rb

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,99 @@
257257
end
258258
RUBY
259259
end
260+
261+
it 'ignores let! used in parent after hook' do
262+
expect_no_offenses(<<~RUBY)
263+
describe 'Parent' do
264+
after { variable.cleanup }
265+
266+
context 'Child' do
267+
let!(:variable) { setup }
268+
it { expect(true).to be true }
269+
end
270+
end
271+
RUBY
272+
end
273+
274+
it 'ignores let! used in parent before hook' do
275+
expect_no_offenses(<<~RUBY)
276+
describe 'Parent' do
277+
before { variable.setup }
278+
279+
context 'Child' do
280+
let!(:variable) { create }
281+
it { expect(true).to be true }
282+
end
283+
end
284+
RUBY
285+
end
286+
287+
it 'ignores let! used in parent around hook' do
288+
expect_no_offenses(<<~RUBY)
289+
describe 'Parent' do
290+
around { |example| variable.wrap { example.run } }
291+
292+
context 'Child' do
293+
let!(:variable) { create }
294+
it { expect(true).to be true }
295+
end
296+
end
297+
RUBY
298+
end
299+
300+
it 'complains when let! is not used anywhere' do
301+
expect_offense(<<~RUBY)
302+
describe 'Parent' do
303+
context 'Child' do
304+
let!(:variable) { setup }
305+
^^^^^^^^^^^^^^^ Do not use `let!` to setup objects not referenced in tests.
306+
it { expect(true).to be true }
307+
end
308+
end
309+
RUBY
310+
end
311+
312+
it 'ignores let! used in nested example' do
313+
expect_no_offenses(<<~RUBY)
314+
describe 'Parent' do
315+
context 'Child' do
316+
let!(:variable) { setup }
317+
it { expect(variable).to be_present }
318+
end
319+
end
320+
RUBY
321+
end
322+
323+
it 'ignores let! used in parent after hook with method call' do
324+
expect_no_offenses(<<~RUBY)
325+
describe 'Example' do
326+
after do
327+
authenticator.remove!
328+
end
329+
330+
context 'nested' do
331+
let!(:authenticator) { create_authenticator }
332+
333+
it 'does something' do
334+
expect(true).to be true
335+
end
336+
end
337+
end
338+
RUBY
339+
end
340+
341+
it 'ignores let! used in multiple parent levels' do
342+
expect_no_offenses(<<~RUBY)
343+
describe 'Level 0' do
344+
after { variable.cleanup }
345+
346+
context 'Level 1' do
347+
context 'Level 2' do
348+
let!(:variable) { setup }
349+
it { expect(true).to be true }
350+
end
351+
end
352+
end
353+
RUBY
354+
end
260355
end

0 commit comments

Comments
 (0)