Skip to content

Commit

Permalink
Add RSpec/NoExpectationExample
Browse files Browse the repository at this point in the history
In response to the review comments, I added the following changes later:

- Prefer x_type? to is_a?
- Use RSpec.Language.Examples
- Add are_expected to expectation method names
- Prefer on_block to on_send
- Add more methods to RSpec.Language.Expectations
- Use RSpec.Language.Expectations
- Replace with clearer custom method names
- Add some missing \@return comments
  • Loading branch information
r7kamura committed Jul 10, 2022
1 parent c8a11ce commit 4db2c25
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 1 deletion.
13 changes: 13 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ Naming/InclusiveLanguage:
Suggestions:
- offense

RSpec:
Language:
Expectations:
- expect_correction
- expect_no_offenses
- expect_offense

RSpec/ExampleLength:
CountAsOne:
- heredoc
Expand All @@ -81,6 +88,12 @@ RSpec/DescribeClass:
Exclude:
- spec/project/**/*.rb

RSpec/MultipleExpectations:
Max: 2

RSpec/NoExpectationExample:
Enabled: true

Style/FormatStringToken:
Exclude:
- spec/rubocop/**/*.rb
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* Fix `RSpec/FilePath` cop missing mismatched expanded namespace. ([@sl4vr][])
* Add new `AllowConsecutiveOneLiners` (default true) option for `Rspec/EmptyLineAfterHook` cop. ([@ngouy][])
* Add autocorrect support for `RSpec/EmptyExampleGroup`. ([@r7kamura][])
* Add `RSpec/NoExpectationExample`. ([@r7kamura][])

## 2.12.1 (2022-07-03)

Expand Down
14 changes: 13 additions & 1 deletion config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,14 @@ RSpec:
Pending:
- pending
Expectations:
- are_expected
- expect
- is_expected
- expect_any_instance_of
- is_expected
- should
- should_not
- should_not_receive
- should_receive
Helpers:
- let
- let!
Expand Down Expand Up @@ -614,6 +619,13 @@ RSpec/NestedGroups:
VersionChanged: '1.10'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NestedGroups

RSpec/NoExpectationExample:
Description: Checks if an example includes any expectation.
Enabled: pending
Safe: false
VersionAdded: '2.13'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample

RSpec/NotToNot:
Description: Checks for consistent method usage for negating expectations.
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
* xref:cops_rspec.adoc#rspecmultiplesubjects[RSpec/MultipleSubjects]
* xref:cops_rspec.adoc#rspecnamedsubject[RSpec/NamedSubject]
* xref:cops_rspec.adoc#rspecnestedgroups[RSpec/NestedGroups]
* xref:cops_rspec.adoc#rspecnoexpectationexample[RSpec/NoExpectationExample]
* xref:cops_rspec.adoc#rspecnottonot[RSpec/NotToNot]
* xref:cops_rspec.adoc#rspecoverwritingsetup[RSpec/OverwritingSetup]
* xref:cops_rspec.adoc#rspecpending[RSpec/Pending]
Expand Down
45 changes: 45 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3284,6 +3284,51 @@ end

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NestedGroups

== RSpec/NoExpectationExample

|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed

| Pending
| No
| Yes (Unsafe)
| 2.13
| -
|===

Checks if an example includes any expectation.

All RSpec's example and expectation methods are covered by default.
If you are using your own custom methods,
add the following configuration:

RSpec:
Language:
Examples:
Regular:
- custom_it
Expectations:
- custom_expect

=== Examples

[source,ruby]
----
# bad
it do
a?
end
# good
it do
expect(a?).to be(true)
end
----

=== References

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample

== RSpec/NotToNot

|===
Expand Down
90 changes: 90 additions & 0 deletions lib/rubocop/cop/rspec/no_expectation_example.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
# Checks if an example includes any expectation.
#
# All RSpec's example and expectation methods are covered by default.
# If you are using your own custom methods,
# add the following configuration:
#
# RSpec:
# Language:
# Examples:
# Regular:
# - custom_it
# Expectations:
# - custom_expect
#
# @example
#
# # bad
# it do
# a?
# end
#
# # good
# it do
# expect(a?).to be(true)
# end
class NoExpectationExample < Base
extend AutoCorrector

include RangeHelp

MSG = 'No expectation found in this example.'

# @!method expectation_method_call?(node)
# @param [RuboCop::AST::Node] node
# @return [Boolean]
def_node_matcher(
:expectation_method_call?,
send_pattern('#Expectations.all')
)

# @param [RuboCop::AST::BlockNode] node
def on_block(node)
return unless example_method_call?(node)
return if including_any_expectation?(node)

add_offense(node) do |corrector|
corrector.remove(removed_range(node))
end
end

private

# @param [RuboCop::AST::BlockNode] node
# @return [Boolean]
def example_method_call?(node)
Examples.all(node.method_name)
end

# Recursively checks if the given node includes any expectation.
# @param [RuboCop::AST::Node] node
# @return [Boolean]
def including_any_expectation?(node)
if !node.is_a?(::RuboCop::AST::Node)
false
elsif expectation_method_call?(node)
true
else
node.children.any? do |child|
including_any_expectation?(child)
end
end
end

# @param [RuboCop::AST::Node] node
# @return [Parser::Source::Range]
def removed_range(node)
range_by_whole_lines(
node.location.expression,
include_final_newline: true
)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
require_relative 'rspec/multiple_subjects'
require_relative 'rspec/named_subject'
require_relative 'rspec/nested_groups'
require_relative 'rspec/no_expectation_example'
require_relative 'rspec/not_to_not'
require_relative 'rspec/overwriting_setup'
require_relative 'rspec/pending'
Expand Down
120 changes: 120 additions & 0 deletions spec/rubocop/cop/rspec/no_expectation_example_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::NoExpectationExample do
context 'with empty example with it' do
it 'registers an offense' do
expect_offense(<<~RUBY)
RSpec.describe Foo do
it { bar }
^^^^^^^^^^ No expectation found in this example.
it { expect(baz).to be_truthy }
end
RUBY

expect_correction(<<~RUBY)
RSpec.describe Foo do
it { expect(baz).to be_truthy }
end
RUBY
end
end

context 'with empty example with specify' do
it 'registers an offense' do
expect_offense(<<~RUBY)
specify { bar }
^^^^^^^^^^^^^^^ No expectation found in this example.
RUBY

expect_correction(<<~RUBY)
RUBY
end
end

context 'with non-empty example with should' do
it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
it { should be_truthy }
RUBY
end
end

context 'with empty examples' do
it 'registers offenses' do
expect_offense(<<~RUBY)
it { bar }
^^^^^^^^^^ No expectation found in this example.
it { baz }
^^^^^^^^^^ No expectation found in this example.
RUBY

expect_correction(<<~RUBY)
RUBY
end
end

context 'with non-empty example with custom expectation' do
it 'registers an offense' do
expect_offense(<<~RUBY)
it { custom_expect(bar) }
^^^^^^^^^^^^^^^^^^^^^^^^^ No expectation found in this example.
RUBY

expect_correction(<<~RUBY)
RUBY
end
end

context 'with non-empty example with configured custom expectation' do
before do
other_cops.dig('RSpec', 'Language', 'Expectations').push('custom_expect')
end

it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
it { custom_expect(bar) }
RUBY
end
end

context 'with empty example with custom example method' do
it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
custom_it { foo }
RUBY
end
end

context 'with empty example with configured custom example method' do
before do
other_cops.dig(
'RSpec',
'Language',
'Examples',
'Regular'
).push('custom_it')
end

it 'registers an offense' do
expect_offense(<<~RUBY)
custom_it { foo }
^^^^^^^^^^^^^^^^^ No expectation found in this example.
RUBY

expect_correction(<<~RUBY)
RUBY
end
end

context 'with block-less example in block' do
it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
RSpec.describe Foo do
pending 'not implemented'
end
RUBY
end
end
end

0 comments on commit 4db2c25

Please sign in to comment.