-
-
Notifications
You must be signed in to change notification settings - Fork 277
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #789 from pirj/add-implicit-block-expectation-cop
Add ImplicitBlockExpectation cop
- Loading branch information
Showing
7 changed files
with
235 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module RSpec | ||
# Check that implicit block expectation syntax is not used. | ||
# | ||
# Prefer using explicit block expectations. | ||
# | ||
# @example | ||
# # bad | ||
# subject { -> { do_something } } | ||
# it { is_expected.to change(something).to(new_value) } | ||
# | ||
# # good | ||
# it 'changes something to a new value' do | ||
# expect { do_something }.to change(something).to(new_value) | ||
# end | ||
class ImplicitBlockExpectation < Cop | ||
MSG = 'Avoid implicit block expectations.' | ||
|
||
def_node_matcher :lambda?, <<-PATTERN | ||
{ | ||
(send (const nil? :Proc) :new) | ||
(send nil? :proc) | ||
(send nil? :lambda) | ||
} | ||
PATTERN | ||
|
||
def_node_matcher :lambda_subject?, '(block #lambda? ...)' | ||
|
||
def_node_matcher :implicit_expect, <<-PATTERN | ||
$(send nil? {:is_expected :should :should_not} ...) | ||
PATTERN | ||
|
||
def on_send(node) | ||
implicit_expect(node) do |implicit_expect| | ||
subject = nearest_subject(implicit_expect) | ||
add_offense(implicit_expect) if lambda_subject?(subject&.body) | ||
end | ||
end | ||
|
||
private | ||
|
||
def nearest_subject(node) | ||
node | ||
.each_ancestor(:block) | ||
.lazy | ||
.select { |block_node| multi_statement_example_group?(block_node) } | ||
.map { |block_node| find_subject(block_node) } | ||
.find(&:itself) | ||
end | ||
|
||
def multi_statement_example_group?(node) | ||
example_group_with_body?(node) && node.body.begin_type? | ||
end | ||
|
||
def find_subject(block_node) | ||
block_node.body.child_nodes.find { |send_node| subject?(send_node) } | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
135 changes: 135 additions & 0 deletions
135
spec/rubocop/cop/rspec/implicit_block_expectation_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
# frozen_string_literal: true | ||
|
||
RSpec.describe RuboCop::Cop::RSpec::ImplicitBlockExpectation do | ||
subject(:cop) { described_class.new } | ||
|
||
it 'flags lambda in subject' do | ||
expect_offense(<<-RUBY) | ||
describe do | ||
subject { -> { boom } } | ||
it { is_expected.to change { something }.to(new_value) } | ||
^^^^^^^^^^^ Avoid implicit block expectations. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'ignores non-lambda subject' do | ||
expect_no_offenses(<<-RUBY) | ||
describe do | ||
subject { 'normal' } | ||
it { is_expected.to eq(something) } | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags lambda in subject!' do | ||
expect_offense(<<-RUBY) | ||
describe do | ||
subject! { -> { boom } } | ||
it { is_expected.to change { something }.to(new_value) } | ||
^^^^^^^^^^^ Avoid implicit block expectations. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags literal lambda' do | ||
expect_offense(<<-RUBY) | ||
describe do | ||
subject! { lambda { boom } } | ||
it { is_expected.to change { something }.to(new_value) } | ||
^^^^^^^^^^^ Avoid implicit block expectations. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags proc' do | ||
expect_offense(<<-RUBY) | ||
describe do | ||
subject! { proc { boom } } | ||
it { is_expected.to change { something }.to(new_value) } | ||
^^^^^^^^^^^ Avoid implicit block expectations. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags Proc.new' do | ||
expect_offense(<<-RUBY) | ||
describe do | ||
subject! { Proc.new { boom } } | ||
it { is_expected.to change { something }.to(new_value) } | ||
^^^^^^^^^^^ Avoid implicit block expectations. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags named subject' do | ||
expect_offense(<<-RUBY) | ||
describe do | ||
subject(:name) { -> { boom } } | ||
it { is_expected.to change { something }.to(new_value) } | ||
^^^^^^^^^^^ Avoid implicit block expectations. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags when subject is defined in the outer example group' do | ||
expect_offense(<<-RUBY) | ||
describe do | ||
subject { -> { boom } } | ||
context do | ||
it { is_expected.to change { something }.to(new_value) } | ||
^^^^^^^^^^^ Avoid implicit block expectations. | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'ignores normal local subject' do | ||
expect_no_offenses(<<-RUBY) | ||
describe do | ||
subject { -> { boom } } | ||
context do | ||
subject { 'normal' } | ||
it { is_expected.to eq(something) } | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'ignores named subject with deeply nested lambda' do | ||
expect_no_offenses(<<-RUBY) | ||
describe do | ||
subject { {hash: -> { boom }} } | ||
it { is_expected.to be(something) } | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags with `should` as implicit subject' do | ||
expect_offense(<<-RUBY) | ||
describe do | ||
subject { -> { boom } } | ||
it { should change { something }.to(new_value) } | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid implicit block expectations. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags with `should_not` as implicit subject' do | ||
expect_offense(<<-RUBY) | ||
describe do | ||
subject { -> { boom } } | ||
it { should_not change { something }.to(new_value) } | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Avoid implicit block expectations. | ||
end | ||
RUBY | ||
end | ||
|
||
it 'ignores when there is no subject defined' do | ||
expect_no_offenses(<<-RUBY) | ||
shared_examples 'subject is defined somewhere else' do | ||
it { is_expected.to change { something }.to(new_value) } | ||
end | ||
RUBY | ||
end | ||
end |