-
-
Notifications
You must be signed in to change notification settings - Fork 277
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ImplicitBlockExpectation cop #789
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're finding the nearest subject, and only then checking if it's a lambda subject. |
||
add_offense(implicit_expect) if lambda_subject?(subject&.body) | ||
end | ||
end | ||
|
||
private | ||
|
||
def nearest_subject(node) | ||
node | ||
.each_ancestor(:block) | ||
.lazy | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
.select { |block_node| multi_statement_example_group?(block_node) } | ||
.map { |block_node| find_subject(block_node) } | ||
.find(&:itself) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line is equivalent to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I think those are equivalent. |
||
end | ||
|
||
def multi_statement_example_group?(node) | ||
example_group_with_body?(node) && node.body.begin_type? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non- |
||
end | ||
|
||
def find_subject(block_node) | ||
block_node.body.child_nodes.find { |send_node| subject?(send_node) } | ||
end | ||
end | ||
end | ||
end | ||
end |
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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The range is not ideal, but I decided to avoid complicating the cop for the sake of highlighting |
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shamelessly borrowed from
ImplicitSubject
. Not sure if it makes sense to extract this with just two usages.