Skip to content
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Master (Unreleased)

- Fix false positive in `RSpec/LetSetup` cop when `let!` variables are used in blocks declared in an outer context. ([@ydah])

## 3.8.0 (2025-11-12)

- Add new cop `RSpec/LeakyLocalVariable`. ([@lovro-bikic])
Expand Down
21 changes: 20 additions & 1 deletion lib/rubocop/cop/rspec/let_setup.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,9 @@ def on_block(node) # rubocop:disable InternalAffairs/NumblockHandler
def unused_let_bang(node)
child_let_bang(node) do |method_send, method_name|
next if overrides_outer_let_bang?(node, method_name)
next if method_called_in_scope?(node, method_name.to_sym)

yield(method_send) unless method_called?(node, method_name.to_sym)
yield(method_send)
end
end

Expand All @@ -98,6 +99,24 @@ def outer_let_bang?(ancestor_node, method_name)
end
end
end

def method_called_in_scope?(node, method_name)
method_called?(node, method_name) ||
method_called_in_parent_hooks?(node, method_name)
end

def method_called_in_parent_hooks?(node, method_name)
node.each_ancestor(:block).any? do |ancestor|
example_or_shared_group_or_including?(ancestor) &&
method_called_in_hooks?(ancestor, method_name)
end
end

def method_called_in_hooks?(node, method_name)
RuboCop::RSpec::ExampleGroup.new(node).hooks.any? do |hook|
method_called?(hook.to_node, method_name)
end
end
end
end
end
Expand Down
95 changes: 95 additions & 0 deletions spec/rubocop/cop/rspec/let_setup_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,99 @@
end
RUBY
end

it 'ignores let! used in parent after hook' do
expect_no_offenses(<<~RUBY)
describe 'Parent' do
after { variable.cleanup }

context 'Child' do
let!(:variable) { setup }
it { expect(true).to be true }
end
end
RUBY
end

it 'ignores let! used in parent before hook' do
expect_no_offenses(<<~RUBY)
describe 'Parent' do
before { variable.setup }

context 'Child' do
let!(:variable) { create }
it { expect(true).to be true }
end
end
RUBY
end

it 'ignores let! used in parent around hook' do
expect_no_offenses(<<~RUBY)
describe 'Parent' do
around { |example| variable.wrap { example.run } }

context 'Child' do
let!(:variable) { create }
it { expect(true).to be true }
end
end
RUBY
end

it 'complains when let! is not used anywhere' do
expect_offense(<<~RUBY)
describe 'Parent' do
context 'Child' do
let!(:variable) { setup }
^^^^^^^^^^^^^^^ Do not use `let!` to setup objects not referenced in tests.
it { expect(true).to be true }
end
end
RUBY
end

it 'ignores let! used in nested example' do
expect_no_offenses(<<~RUBY)
describe 'Parent' do
context 'Child' do
let!(:variable) { setup }
it { expect(variable).to be_present }
end
end
RUBY
end

it 'ignores let! used in parent after hook with method call' do
expect_no_offenses(<<~RUBY)
describe 'Example' do
after do
authenticator.remove!
end

context 'nested' do
let!(:authenticator) { create_authenticator }

it 'does something' do
expect(true).to be true
end
end
end
RUBY
end

it 'ignores let! used in multiple parent levels' do
expect_no_offenses(<<~RUBY)
describe 'Level 0' do
after { variable.cleanup }

context 'Level 1' do
context 'Level 2' do
let!(:variable) { setup }
it { expect(true).to be true }
end
end
end
RUBY
end
end