Skip to content

Commit 02e41c7

Browse files
committed
Add ImplicitBlockExpectation cop
Based on https://rspec.rubystyle.guide/#implicit-block-expectations
1 parent 2475a64 commit 02e41c7

File tree

7 files changed

+235
-0
lines changed

7 files changed

+235
-0
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+
* Add `RSpec/ImplicitBlockExpectation` cop. ([@pirj][])
6+
57
## 1.34.1 (2019-07-31)
68

79
* Fix `RSpec/DescribedClass`'s error when a local variable is part of the namespace. ([@pirj][])

config/default.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@ RSpec/HooksBeforeExamples:
197197
Description: Checks for before/around/after hooks that come after an example.
198198
StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/HooksBeforeExamples
199199

200+
RSpec/ImplicitBlockExpectation:
201+
Description: Check that implicit block expectation syntax is not used.
202+
Enabled: true
203+
StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitBlockExpectation
204+
200205
RSpec/ImplicitExpect:
201206
Description: Check that a consistent implicit expectation style is used.
202207
Enabled: true
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module RSpec
6+
# Check that implicit block expectation syntax is not used.
7+
#
8+
# Prefer using explicit block expectations.
9+
#
10+
# @example
11+
# # bad
12+
# subject { -> { do_something } }
13+
# it { is_expected.to change(something).to(new_value) }
14+
#
15+
# # good
16+
# it 'changes something to a new value' do
17+
# expect { do_something }.to change(something).to(new_value)
18+
# end
19+
class ImplicitBlockExpectation < Cop
20+
MSG = 'Avoid implicit block expectations.'
21+
22+
def_node_matcher :lambda?, <<-PATTERN
23+
{
24+
(send (const nil? :Proc) :new)
25+
(send nil? :proc)
26+
(send nil? :lambda)
27+
}
28+
PATTERN
29+
30+
def_node_matcher :lambda_subject?, '(block #lambda? ...)'
31+
32+
def_node_matcher :implicit_expect, <<-PATTERN
33+
$(send nil? {:is_expected :should :should_not} ...)
34+
PATTERN
35+
36+
def on_send(node)
37+
implicit_expect(node) do |implicit_expect|
38+
subject = nearest_subject(implicit_expect)
39+
add_offense(implicit_expect) if lambda_subject?(subject&.body)
40+
end
41+
end
42+
43+
private
44+
45+
def nearest_subject(node)
46+
node
47+
.each_ancestor(:block)
48+
.lazy
49+
.select { |block_node| multi_statement_example_group?(block_node) }
50+
.map { |block_node| find_subject(block_node) }
51+
.find(&:itself)
52+
end
53+
54+
def multi_statement_example_group?(node)
55+
example_group_with_body?(node) && node.body.begin_type?
56+
end
57+
58+
def find_subject(block_node)
59+
block_node.body.child_nodes.find { |send_node| subject?(send_node) }
60+
end
61+
end
62+
end
63+
end
64+
end

lib/rubocop/cop/rspec_cops.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
require_relative 'rspec/focus'
4242
require_relative 'rspec/hook_argument'
4343
require_relative 'rspec/hooks_before_examples'
44+
require_relative 'rspec/implicit_block_expectation'
4445
require_relative 'rspec/implicit_expect'
4546
require_relative 'rspec/implicit_subject'
4647
require_relative 'rspec/instance_spy'

manual/cops.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
* [RSpec/Focus](cops_rspec.md#rspecfocus)
4141
* [RSpec/HookArgument](cops_rspec.md#rspechookargument)
4242
* [RSpec/HooksBeforeExamples](cops_rspec.md#rspechooksbeforeexamples)
43+
* [RSpec/ImplicitBlockExpectation](cops_rspec.md#rspecimplicitblockexpectation)
4344
* [RSpec/ImplicitExpect](cops_rspec.md#rspecimplicitexpect)
4445
* [RSpec/ImplicitSubject](cops_rspec.md#rspecimplicitsubject)
4546
* [RSpec/InstanceSpy](cops_rspec.md#rspecinstancespy)

manual/cops_rspec.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1169,6 +1169,33 @@ end
11691169

11701170
* [http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/HooksBeforeExamples](http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/HooksBeforeExamples)
11711171

1172+
## RSpec/ImplicitBlockExpectation
1173+
1174+
Enabled by default | Supports autocorrection
1175+
--- | ---
1176+
Enabled | No
1177+
1178+
Check that implicit block expectation syntax is not used.
1179+
1180+
Prefer using explicit block expectations.
1181+
1182+
### Examples
1183+
1184+
```ruby
1185+
# bad
1186+
subject { -> { do_something } }
1187+
it { is_expected.to change(something).to(new_value) }
1188+
1189+
# good
1190+
it 'changes something to a new value' do
1191+
expect { do_something }.to change(something).to(new_value)
1192+
end
1193+
```
1194+
1195+
### References
1196+
1197+
* [http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitBlockExpectation](http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ImplicitBlockExpectation)
1198+
11721199
## RSpec/ImplicitExpect
11731200

11741201
Enabled by default | Supports autocorrection
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# frozen_string_literal: true
2+
3+
RSpec.describe RuboCop::Cop::RSpec::ImplicitBlockExpectation do
4+
subject(:cop) { described_class.new }
5+
6+
it 'flags lambda in subject' do
7+
expect_offense(<<-RUBY)
8+
describe do
9+
subject { -> { boom } }
10+
it { is_expected.to change { something }.to(new_value) }
11+
^^^^^^^^^^^ Avoid implicit block expectations.
12+
end
13+
RUBY
14+
end
15+
16+
it 'ignores non-lambda subject' do
17+
expect_no_offenses(<<-RUBY)
18+
describe do
19+
subject { 'normal' }
20+
it { is_expected.to eq(something) }
21+
end
22+
RUBY
23+
end
24+
25+
it 'flags lambda in subject!' do
26+
expect_offense(<<-RUBY)
27+
describe do
28+
subject! { -> { boom } }
29+
it { is_expected.to change { something }.to(new_value) }
30+
^^^^^^^^^^^ Avoid implicit block expectations.
31+
end
32+
RUBY
33+
end
34+
35+
it 'flags literal lambda' do
36+
expect_offense(<<-RUBY)
37+
describe do
38+
subject! { lambda { boom } }
39+
it { is_expected.to change { something }.to(new_value) }
40+
^^^^^^^^^^^ Avoid implicit block expectations.
41+
end
42+
RUBY
43+
end
44+
45+
it 'flags proc' do
46+
expect_offense(<<-RUBY)
47+
describe do
48+
subject! { proc { boom } }
49+
it { is_expected.to change { something }.to(new_value) }
50+
^^^^^^^^^^^ Avoid implicit block expectations.
51+
end
52+
RUBY
53+
end
54+
55+
it 'flags Proc.new' do
56+
expect_offense(<<-RUBY)
57+
describe do
58+
subject! { Proc.new { boom } }
59+
it { is_expected.to change { something }.to(new_value) }
60+
^^^^^^^^^^^ Avoid implicit block expectations.
61+
end
62+
RUBY
63+
end
64+
65+
it 'flags named subject' do
66+
expect_offense(<<-RUBY)
67+
describe do
68+
subject(:name) { -> { boom } }
69+
it { is_expected.to change { something }.to(new_value) }
70+
^^^^^^^^^^^ Avoid implicit block expectations.
71+
end
72+
RUBY
73+
end
74+
75+
it 'flags when subject is defined in the outer example group' do
76+
expect_offense(<<-RUBY)
77+
describe do
78+
subject { -> { boom } }
79+
context do
80+
it { is_expected.to change { something }.to(new_value) }
81+
^^^^^^^^^^^ Avoid implicit block expectations.
82+
end
83+
end
84+
RUBY
85+
end
86+
87+
it 'ignores normal local subject' do
88+
expect_no_offenses(<<-RUBY)
89+
describe do
90+
subject { -> { boom } }
91+
context do
92+
subject { 'normal' }
93+
it { is_expected.to eq(something) }
94+
end
95+
end
96+
RUBY
97+
end
98+
99+
it 'ignores named subject with deeply nested lambda' do
100+
expect_no_offenses(<<-RUBY)
101+
describe do
102+
subject { {hash: -> { boom }} }
103+
it { is_expected.to be(something) }
104+
end
105+
RUBY
106+
end
107+
108+
it 'flags with `should` as implicit subject' do
109+
expect_offense(<<-RUBY)
110+
describe do
111+
subject { -> { boom } }
112+
it { should change { something }.to(new_value) }
113+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid implicit block expectations.
114+
end
115+
RUBY
116+
end
117+
118+
it 'flags with `should_not` as implicit subject' do
119+
expect_offense(<<-RUBY)
120+
describe do
121+
subject { -> { boom } }
122+
it { should_not change { something }.to(new_value) }
123+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid implicit block expectations.
124+
end
125+
RUBY
126+
end
127+
128+
it 'ignores when there is no subject defined' do
129+
expect_no_offenses(<<-RUBY)
130+
shared_examples 'subject is defined somewhere else' do
131+
it { is_expected.to change { something }.to(new_value) }
132+
end
133+
RUBY
134+
end
135+
end

0 commit comments

Comments
 (0)