Skip to content
Merged
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 a false positive for `RSpec/LeakyLocalVariable` when variables are used only in example metadata (e.g., skip messages). ([@ydah])

## 3.8.0 (2025-11-12)

- Add new cop `RSpec/LeakyLocalVariable`. ([@lovro-bikic])
Expand Down
7 changes: 7 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3474,6 +3474,13 @@ it "#{attribute} is persisted" do
expectations
end

# good - when variable is used only in example metadata
skip_message = 'not yet implemented'

it 'does something', skip: skip_message do
expectations
end

# good - when variable is used only to include other examples
examples = foo ? 'some examples' : 'other examples'

Expand Down
18 changes: 18 additions & 0 deletions lib/rubocop/cop/rspec/leaky_local_variable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,13 @@ module RSpec
# expectations
# end
#
# # good - when variable is used only in example metadata
# skip_message = 'not yet implemented'
#
# it 'does something', skip: skip_message do
# expectations
# end
#
# # good - when variable is used only to include other examples
# examples = foo ? 'some examples' : 'other examples'
#
Expand Down Expand Up @@ -103,6 +110,8 @@ def check_references(variable)
def allowed_reference?(node)
node.each_ancestor.any? do |ancestor|
next true if example_method?(ancestor)
next true if in_example_arguments?(ancestor, node)

if includes_method?(ancestor)
next allowed_includes_arguments?(ancestor, node)
end
Expand All @@ -111,6 +120,15 @@ def allowed_reference?(node)
end
end

def in_example_arguments?(ancestor, node)
return false unless ancestor.send_type?
return false unless Examples.all(ancestor.method_name)

ancestor.arguments.any? do |arg|
arg.equal?(node) || arg.each_descendant.any?(node)
end
end

def allowed_includes_arguments?(node, argument)
node.arguments[1..].all? do |argument_node|
next true if argument_node.type?(:dstr, :dsym)
Expand Down
40 changes: 40 additions & 0 deletions spec/rubocop/cop/rspec/leaky_local_variable_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -316,4 +316,44 @@
end
RUBY
end

it 'does not register an offense when variable is used only in skip ' \
'metadata' do
expect_no_offenses(<<~RUBY)
describe SomeClass do
skip_message = 'not yet implemented'

it 'does something', skip: skip_message do
expect(1 + 2).to eq(3)
end
end
RUBY
end

it 'does not register an offense when variable is used only in pending ' \
'metadata' do
expect_no_offenses(<<~RUBY)
describe SomeClass do
pending_message = 'work in progress'

it 'does something', pending: pending_message do
expect(1 + 2).to eq(3)
end
end
RUBY
end

it 'registers an offense when variable is used in skip metadata and in ' \
'block body' do
expect_offense(<<~RUBY)
describe SomeClass do
skip_message = 'not yet implemented'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not use local variables defined outside of examples inside of them.

it 'does something', skip: skip_message do
puts skip_message
end
end
RUBY
end
end