Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ddaddea

Browse filesBrowse files
committed
Add new cop RSpec/LeakyLocalVariable
1 parent 413770b commit ddaddea
Copy full SHA for ddaddea

File tree

Expand file treeCollapse file tree

8 files changed

+550
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

8 files changed

+550
-0
lines changed
Open diff view settings
Collapse file

‎.rubocop.yml‎

Copy file name to clipboardExpand all lines: .rubocop.yml
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,4 @@ Performance/ZipWithoutBlock: {Enabled: true}
293293
# Enable our own pending cops.
294294

295295
RSpec/IncludeExamples: {Enabled: true}
296+
RSpec/LeakyLocalVariable: {Enabled: true}
Collapse file

‎CHANGELOG.md‎

Copy file name to clipboardExpand all lines: CHANGELOG.md
+2Lines changed: 2 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Master (Unreleased)
44

5+
- Add new cop `RSpec/LeakyLocalVariable`. ([@lovro-bikic])
6+
57
## 3.7.0 (2025-09-01)
68

79
- Mark `RSpec/IncludeExamples` as `SafeAutoCorrect: false`. ([@yujideveloper])
Collapse file

‎config/default.yml‎

Copy file name to clipboardExpand all lines: config/default.yml
+6Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,12 @@ RSpec/LeakyConstantDeclaration:
610610
StyleGuide: https://rspec.rubystyle.guide/#declare-constants
611611
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeakyConstantDeclaration
612612

613+
RSpec/LeakyLocalVariable:
614+
Description: Checks for local variables from outer scopes used inside examples.
615+
Enabled: pending
616+
VersionAdded: "<<next>>"
617+
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeakyLocalVariable
618+
613619
RSpec/LetBeforeExamples:
614620
Description: Checks for `let` definitions that come after an example.
615621
Enabled: true
Collapse file

‎docs/modules/ROOT/pages/cops.adoc‎

Copy file name to clipboardExpand all lines: docs/modules/ROOT/pages/cops.adoc
+1Lines changed: 1 addition & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
* xref:cops_rspec.adoc#rspeciteratedexpectation[RSpec/IteratedExpectation]
6060
* xref:cops_rspec.adoc#rspecleadingsubject[RSpec/LeadingSubject]
6161
* xref:cops_rspec.adoc#rspecleakyconstantdeclaration[RSpec/LeakyConstantDeclaration]
62+
* xref:cops_rspec.adoc#rspecleakylocalvariable[RSpec/LeakyLocalVariable]
6263
* xref:cops_rspec.adoc#rspecletbeforeexamples[RSpec/LetBeforeExamples]
6364
* xref:cops_rspec.adoc#rspecletsetup[RSpec/LetSetup]
6465
* xref:cops_rspec.adoc#rspecmatcharray[RSpec/MatchArray]
Collapse file

‎docs/modules/ROOT/pages/cops_rspec.adoc‎

Copy file name to clipboardExpand all lines: docs/modules/ROOT/pages/cops_rspec.adoc
+82Lines changed: 82 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -3390,6 +3390,88 @@ end
33903390
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeakyConstantDeclaration
33913391
* https://rspec.info/features/3-12/rspec-mocks/mutating-constants
33923392
3393+
[#rspecleakylocalvariable]
3394+
== RSpec/LeakyLocalVariable
3395+
3396+
|===
3397+
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed
3398+
3399+
| Pending
3400+
| Yes
3401+
| No
3402+
| <<next>>
3403+
| -
3404+
|===
3405+
3406+
Checks for local variables from outer scopes used inside examples.
3407+
3408+
Local variables assigned outside an example but used within it act
3409+
as shared state, which can make tests non-deterministic.
3410+
3411+
[#examples-rspecleakylocalvariable]
3412+
=== Examples
3413+
3414+
[source,ruby]
3415+
----
3416+
# bad - outside variable used in a hook
3417+
user = create(:user)
3418+
3419+
before { user.update(admin: true) }
3420+
3421+
# good
3422+
let(:user) { create(:user) }
3423+
3424+
before { user.update(admin: true) }
3425+
3426+
# bad - outside variable used in an example
3427+
user = create(:user)
3428+
3429+
it 'is persisted' do
3430+
expect(user).to be_persisted
3431+
end
3432+
3433+
# good
3434+
let(:user) { create(:user) }
3435+
3436+
it 'is persisted' do
3437+
expect(user).to be_persisted
3438+
end
3439+
3440+
# also good - assigning the variable within the example
3441+
it 'is persisted' do
3442+
user = create(:user)
3443+
3444+
expect(user).to be_persisted
3445+
end
3446+
3447+
# bad - outside variable passed to included examples
3448+
attrs = ['foo', 'bar']
3449+
3450+
it_behaves_like 'some examples', attrs
3451+
3452+
# good
3453+
it_behaves_like 'some examples' do
3454+
let(:attrs) { ['foo', 'bar'] }
3455+
end
3456+
3457+
# good - when variable is used only as example description
3458+
attribute = 'foo'
3459+
3460+
it "#{attribute} is persisted" do
3461+
expectations
3462+
end
3463+
3464+
# good - when variable is used only to include other examples
3465+
examples = foo ? 'some examples' : 'other examples'
3466+
3467+
it_behaves_like examples, another_argument
3468+
----
3469+
3470+
[#references-rspecleakylocalvariable]
3471+
=== References
3472+
3473+
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/LeakyLocalVariable
3474+
33933475
[#rspecletbeforeexamples]
33943476
== RSpec/LetBeforeExamples
33953477
Collapse file
+138Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module RSpec
6+
# Checks for local variables from outer scopes used inside examples.
7+
#
8+
# Local variables assigned outside an example but used within it act
9+
# as shared state, which can make tests non-deterministic.
10+
#
11+
# @example
12+
# # bad - outside variable used in a hook
13+
# user = create(:user)
14+
#
15+
# before { user.update(admin: true) }
16+
#
17+
# # good
18+
# let(:user) { create(:user) }
19+
#
20+
# before { user.update(admin: true) }
21+
#
22+
# # bad - outside variable used in an example
23+
# user = create(:user)
24+
#
25+
# it 'is persisted' do
26+
# expect(user).to be_persisted
27+
# end
28+
#
29+
# # good
30+
# let(:user) { create(:user) }
31+
#
32+
# it 'is persisted' do
33+
# expect(user).to be_persisted
34+
# end
35+
#
36+
# # also good - assigning the variable within the example
37+
# it 'is persisted' do
38+
# user = create(:user)
39+
#
40+
# expect(user).to be_persisted
41+
# end
42+
#
43+
# # bad - outside variable passed to included examples
44+
# attrs = ['foo', 'bar']
45+
#
46+
# it_behaves_like 'some examples', attrs
47+
#
48+
# # good
49+
# it_behaves_like 'some examples' do
50+
# let(:attrs) { ['foo', 'bar'] }
51+
# end
52+
#
53+
# # good - when variable is used only as example description
54+
# attribute = 'foo'
55+
#
56+
# it "#{attribute} is persisted" do
57+
# expectations
58+
# end
59+
#
60+
# # good - when variable is used only to include other examples
61+
# examples = foo ? 'some examples' : 'other examples'
62+
#
63+
# it_behaves_like examples, another_argument
64+
#
65+
class LeakyLocalVariable < Base
66+
MSG = 'Do not use local variables defined outside of ' \
67+
'examples inside of them.'
68+
69+
# @!method example_method?(node)
70+
def_node_matcher :example_method?, <<~PATTERN
71+
(send nil? #Examples.all _)
72+
PATTERN
73+
74+
# @!method includes_method?(node)
75+
def_node_matcher :includes_method?, <<~PATTERN
76+
(send nil? #Includes.all ...)
77+
PATTERN
78+
79+
def self.joining_forces
80+
VariableForce
81+
end
82+
83+
def after_leaving_scope(scope, _variable_table)
84+
scope.variables.each_value { |variable| check_references(variable) }
85+
end
86+
87+
private
88+
89+
def check_references(variable)
90+
variable.assignments.each do |assignment|
91+
next if part_of_example_scope?(assignment.node)
92+
93+
assignment.references.each do |reference|
94+
next unless inside_describe_block?(reference)
95+
next unless part_of_example_scope?(reference)
96+
next if allowed_reference?(reference)
97+
98+
add_offense(assignment.node)
99+
end
100+
end
101+
end
102+
103+
def allowed_reference?(node)
104+
node.each_ancestor.any? do |ancestor|
105+
next true if example_method?(ancestor)
106+
if includes_method?(ancestor)
107+
next allowed_includes_arguments?(ancestor, node)
108+
end
109+
110+
false
111+
end
112+
end
113+
114+
def allowed_includes_arguments?(node, argument)
115+
node.arguments[1..].all? do |argument_node|
116+
next true if argument_node.type?(:dstr, :dsym)
117+
118+
argument_node != argument &&
119+
argument_node.each_descendant.none?(argument)
120+
end
121+
end
122+
123+
def part_of_example_scope?(node)
124+
node.each_ancestor.any? { |ancestor| example_scope?(ancestor) }
125+
end
126+
127+
def example_scope?(node)
128+
subject?(node) || let?(node) || hook?(node) || example?(node) ||
129+
include?(node)
130+
end
131+
132+
def inside_describe_block?(node)
133+
node.each_ancestor(:block).any? { |ancestor| spec_group?(ancestor) }
134+
end
135+
end
136+
end
137+
end
138+
end
Collapse file

‎lib/rubocop/cop/rspec_cops.rb‎

Copy file name to clipboardExpand all lines: lib/rubocop/cop/rspec_cops.rb
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
require_relative 'rspec/iterated_expectation'
5858
require_relative 'rspec/leading_subject'
5959
require_relative 'rspec/leaky_constant_declaration'
60+
require_relative 'rspec/leaky_local_variable'
6061
require_relative 'rspec/let_before_examples'
6162
require_relative 'rspec/let_setup'
6263
require_relative 'rspec/match_array'

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.