Skip to content

Commit

Permalink
Add VerifiedDoubleReference cop
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Noe authored and pirj committed Mar 21, 2022
1 parent 072ffe9 commit 1ec40e3
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

## Master (Unreleased)
* Add `RSpec/VerifiedDoubleReference` cop. ([@t3h2mas][])

* Fix a false positive for `RSpec/EmptyExampleGroup` when expectations in case statement. ([@ydah][])

Expand Down Expand Up @@ -675,3 +676,4 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features.
[@harry-graham]: https://github.com/harry-graham
[@oshiro3]: https://github.com/oshiro3
[@ydah]: https://github.com/ydah
[@t3h2mas]: https://github.com/t3h2mas
10 changes: 10 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,16 @@ RSpec/VariableName:
VersionChanged: '1.43'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VariableName

RSpec/VerifiedDoubleReference:
Description: Checks for consistent verified double reference style.
Enabled: pending
EnforcedStyle: constant
SupportedStyles:
- constant
- string
VersionAdded: 2.10.0
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/VerifiedDoubleReference

RSpec/VerifiedDoubles:
Description: Prefer using verifying doubles over normal doubles.
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 @@ -83,6 +83,7 @@
* xref:cops_rspec.adoc#rspecunspecifiedexception[RSpec/UnspecifiedException]
* xref:cops_rspec.adoc#rspecvariabledefinition[RSpec/VariableDefinition]
* xref:cops_rspec.adoc#rspecvariablename[RSpec/VariableName]
* xref:cops_rspec.adoc#rspecverifieddoublereference[RSpec/VerifiedDoubleReference]
* xref:cops_rspec.adoc#rspecverifieddoubles[RSpec/VerifiedDoubles]
* xref:cops_rspec.adoc#rspecvoidexpect[RSpec/VoidExpect]
* xref:cops_rspec.adoc#rspecyield[RSpec/Yield]
Expand Down
75 changes: 75 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4300,6 +4300,81 @@ let(:userFood_2) { 'fettuccine' }

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

== RSpec/VerifiedDoubleReference

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

| Pending
| Yes
| Yes
| 2.10.0
| -
|===

Checks for consistent verified double reference style.

Only investigates references that are one of the supported styles.

This cop can be configured in your configuration using the
`EnforcedStyle` option and supports `--auto-gen-config`.

=== Examples

==== `EnforcedStyle: constant` (default)

[source,ruby]
----
# bad
let(:foo) do
instance_double('ClassName', method_name: 'returned_value')
end
# good
let(:foo) do
instance_double(ClassName, method_name: 'returned_value')
end
----

==== `EnforcedStyle: string`

[source,ruby]
----
# bad
let(:foo) do
instance_double(ClassName, method_name: 'returned_value')
end
# good
let(:foo) do
instance_double('ClassName', method_name: 'returned_value')
end
----

==== Reference is not in the supported style list. No enforcement

[source,ruby]
----
# good
let(:foo) do
instance_double(@klass, method_name: 'returned_value')
end
----

=== Configurable attributes

|===
| Name | Default value | Configurable values

| EnforcedStyle
| `constant`
| `constant`, `string`
|===

=== References

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

== RSpec/VerifiedDoubles

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

module RuboCop
module Cop
module RSpec
# Checks for consistent verified double reference style.
#
# Only investigates references that are one of the supported styles.
#
# @see https://relishapp.com/rspec/rspec-mocks/docs/verifying-doubles
#
# This cop can be configured in your configuration using the
# `EnforcedStyle` option and supports `--auto-gen-config`.
#
# @example `EnforcedStyle: constant` (default)
# # bad
# let(:foo) do
# instance_double('ClassName', method_name: 'returned_value')
# end
#
# # good
# let(:foo) do
# instance_double(ClassName, method_name: 'returned_value')
# end
#
# @example `EnforcedStyle: string`
# # bad
# let(:foo) do
# instance_double(ClassName, method_name: 'returned_value')
# end
#
# # good
# let(:foo) do
# instance_double('ClassName', method_name: 'returned_value')
# end
#
# @example Reference is not in the supported style list. No enforcement
#
# # good
# let(:foo) do
# instance_double(@klass, method_name: 'returned_value')
# end
class VerifiedDoubleReference < Base
extend AutoCorrector
include ConfigurableEnforcedStyle

MSG = 'Use a %<style>s class reference for verified doubles.'

RESTRICT_ON_SEND = Set[
:class_double,
:class_spy,
:instance_double,
:instance_spy,
:mock_model,
:object_double,
:object_spy,
:stub_model
].freeze

REFERENCE_TYPE_STYLES = {
str: :string,
const: :constant
}.freeze

# @!method verified_double(node)
def_node_matcher :verified_double, <<~PATTERN
(send
nil?
RESTRICT_ON_SEND
$_class_reference
...)
PATTERN

def on_send(node)
verified_double(node) do |class_reference|
break correct_style_detected unless opposing_style?(class_reference)

message = format(MSG, style: style)
expression = class_reference.loc.expression

add_offense(expression, message: message) do |corrector|
violation = class_reference.children.last.to_s
corrector.replace(expression, correct_style(violation))

opposite_style_detected
end
end
end

private

def opposing_style?(class_reference)
class_reference_style = REFERENCE_TYPE_STYLES[class_reference.type]

# Only enforce supported styles
return false unless class_reference_style

class_reference_style != style
end

def correct_style(violation)
if style == :string
"'#{violation}'"
else
violation
end
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 @@ -97,6 +97,7 @@
require_relative 'rspec/unspecified_exception'
require_relative 'rspec/variable_definition'
require_relative 'rspec/variable_name'
require_relative 'rspec/verified_double_reference'
require_relative 'rspec/verified_doubles'
require_relative 'rspec/void_expect'
require_relative 'rspec/yield'
82 changes: 82 additions & 0 deletions spec/rubocop/cop/rspec/verified_double_reference_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::VerifiedDoubleReference do
verified_doubles = %w[
class_double
class_spy
instance_double
instance_spy
mock_model
object_double
object_spy
stub_model
]

verified_doubles.each do |verified_double|
describe verified_double do
context 'when EnforcedStyle is constant' do
let(:cop_config) do
{ 'EnforcedStyle' => 'constant' }
end

it 'does not flag a violation when using a constant reference' do
expect_no_offenses("#{verified_double}(ClassName)")
end

it 'flags a violation when using a string reference' do
expect_offense(<<~RUBY, verified_double: verified_double)
%{verified_double}('ClassName')
_{verified_double} ^^^^^^^^^^^ Use a constant class reference for verified doubles.
RUBY

expect_correction(<<~RUBY)
#{verified_double}(ClassName)
RUBY
end

include_examples 'detects style',
"#{verified_double}(ClassName)",
'constant'
end

context 'when EnforcedStyle is string' do
let(:cop_config) do
{ 'EnforcedStyle' => 'string' }
end

it 'does not flag a violation when using a string reference' do
expect_no_offenses("#{verified_double}('ClassName')")
end

it 'flags a violation when using a constant reference' do
expect_offense(<<~RUBY, verified_double: verified_double)
%{verified_double}(ClassName)
_{verified_double} ^^^^^^^^^ Use a string class reference for verified doubles.
RUBY

expect_correction(<<~RUBY)
#{verified_double}('ClassName')
RUBY
end

include_examples 'detects style',
"#{verified_double}('ClassName')",
'string'
end
end
end

it 'does not flag a violation when reference is not a supported style' do
expect_no_offenses(<<~RUBY)
klass = Array
instance_double(klass)
@sut = Array
let(:double) { instance_double(@sut) }
object_double([])
class_double(:Model)
RUBY
end
end

0 comments on commit 1ec40e3

Please sign in to comment.