Skip to content
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 RSpec/ClassCheck cop #1357

Merged
merged 1 commit into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ RSpec/BeNil:
Enabled: true
RSpec/ChangeByZero:
Enabled: true
RSpec/ClassCheck:
Enabled: true

This comment was marked as resolved.

This comment was marked as resolved.

RSpec/ExcessiveDocstringSpacing:
Enabled: true
RSpec/IdenticalEqualityAssertion:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* Add `AllowedGroups` configuration option to `RSpec/NestedGroups`. ([@ydah][])
* Deprecate `IgnoredPatterns` option in favor of the `AllowedPatterns` options. ([@ydah][])
* Add `AllowedPatterns` configuration option to `RSpec/ContextWording`. ([@ydah][])
* Add `RSpec/ClassCheck` cop. ([@r7kamura][])

## 2.12.1 (2022-07-03)

Expand Down
11 changes: 11 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,17 @@ RSpec/ChangeByZero:
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ChangeByZero
NegatedMatcher: ~

RSpec/ClassCheck:
Description: Enforces consistent use of `be_a` or `be_kind_of`.
StyleGuide: "#is-a-vs-kind-of"
Enabled: pending
VersionAdded: '2.13'
EnforcedStyle: be_a
SupportedStyles:
- be_a
- be_kind_of
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ClassCheck

RSpec/ContextMethod:
Description: "`context` should not be used for specifying methods."
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 @@ -12,6 +12,7 @@
* xref:cops_rspec.adoc#rspecbenil[RSpec/BeNil]
* xref:cops_rspec.adoc#rspecbeforeafterall[RSpec/BeforeAfterAll]
* xref:cops_rspec.adoc#rspecchangebyzero[RSpec/ChangeByZero]
* xref:cops_rspec.adoc#rspecclasscheck[RSpec/ClassCheck]
* xref:cops_rspec.adoc#rspeccontextmethod[RSpec/ContextMethod]
* xref:cops_rspec.adoc#rspeccontextwording[RSpec/ContextWording]
* xref:cops_rspec.adoc#rspecdescribeclass[RSpec/DescribeClass]
Expand Down
57 changes: 57 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,63 @@ expect { run }

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

== RSpec/ClassCheck

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

| Pending
| Yes
| Yes
| 2.13
| -
|===

Enforces consistent use of `be_a` or `be_kind_of`.

=== Examples

==== EnforcedStyle: be_a (default)

[source,ruby]
----
# bad
expect(object).to be_kind_of(String)
expect(object).to be_a_kind_of(String)

# good
expect(object).to be_a(String)
expect(object).to be_an(String)
----

==== EnforcedStyle: be_kind_of

[source,ruby]
----
# bad
expect(object).to be_a(String)
expect(object).to be_an(String)

# good
expect(object).to be_kind_of(String)
expect(object).to be_a_kind_of(String)
----

=== Configurable attributes

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

| EnforcedStyle
| `be_a`
| `be_a`, `be_kind_of`
|===

=== References

* https://rubystyle.guide#is-a-vs-kind-of
* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/ClassCheck

== RSpec/ContextMethod

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

module RuboCop
module Cop
module RSpec
# Enforces consistent use of `be_a` or `be_kind_of`.
#
# @example EnforcedStyle: be_a (default)
# # bad
# expect(object).to be_kind_of(String)
# expect(object).to be_a_kind_of(String)
#
# # good
# expect(object).to be_a(String)
# expect(object).to be_an(String)
#
# @example EnforcedStyle: be_kind_of
# # bad
# expect(object).to be_a(String)
# expect(object).to be_an(String)
#
# # good
# expect(object).to be_kind_of(String)
# expect(object).to be_a_kind_of(String)
#
class ClassCheck < Base
extend AutoCorrector
include ConfigurableEnforcedStyle

MSG = 'Prefer `%<preferred>s` over `%<current>s`.'

METHOD_NAMES_FOR_BE_A = ::Set[
:be_a,
:be_an
].freeze

METHOD_NAMES_FOR_KIND_OF = ::Set[
:be_a_kind_of,
:be_kind_of
].freeze

PREFERRED_METHOD_NAME_BY_STYLE = {
be_a: :be_a,
be_kind_of: :be_kind_of
}.freeze

RESTRICT_ON_SEND = %i[
be_a
Darhazer marked this conversation as resolved.
Show resolved Hide resolved
be_a_kind_of
be_an
be_kind_of
].freeze

def on_send(node)
return unless offending?(node)

add_offense(
node.loc.selector,
message: format_message(node)
) do |corrector|
autocorrect(corrector, node)
end
end

private

def autocorrect(corrector, node)
corrector.replace(node.loc.selector, preferred_method_name)
end

def format_message(node)
format(
MSG,
current: node.method_name,
preferred: preferred_method_name
)
end

def offending?(node)
!node.receiver && !preferred_method_name?(node.method_name)
end

def preferred_method_name?(method_name)
preferred_method_names.include?(method_name)
end

def preferred_method_name
PREFERRED_METHOD_NAME_BY_STYLE[style]
end

def preferred_method_names
if style == :be_a
METHOD_NAMES_FOR_BE_A
else
METHOD_NAMES_FOR_KIND_OF
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 @@ -29,6 +29,7 @@
require_relative 'rspec/be_nil'
require_relative 'rspec/before_after_all'
require_relative 'rspec/change_by_zero'
require_relative 'rspec/class_check'
require_relative 'rspec/context_method'
require_relative 'rspec/context_wording'
require_relative 'rspec/describe_class'
Expand Down
103 changes: 103 additions & 0 deletions spec/rubocop/cop/rspec/class_check_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::ClassCheck do
context 'when EnforcedStyle is `be_a`' do
context 'when `be_a` is used' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
expect(a).to be_a(b)
RUBY
end
end

context 'when `be_an` is used' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
expect(a).to be_an(b)
RUBY
end
end

context 'when `Foo.be_kind_of` is used' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
Foo.be_kind_of(b)
RUBY
end
end

context 'when `be_kind_of` is used' do
it 'registers and corrects an offense' do
expect_offense(<<~RUBY)
expect(a).to be_kind_of(b)
^^^^^^^^^^ Prefer `be_a` over `be_kind_of`.
RUBY

expect_correction(<<~RUBY)
expect(a).to be_a(b)
RUBY
end
end

context 'when `be_a_kind_of` is used' do
it 'registers and corrects an offense' do
expect_offense(<<~RUBY)
expect(a).to be_a_kind_of(b)
^^^^^^^^^^^^ Prefer `be_a` over `be_a_kind_of`.
RUBY

expect_correction(<<~RUBY)
expect(a).to be_a(b)
RUBY
end
end
end

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

context 'when `be_kind_of` is used' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
expect(a).to be_kind_of(b)
RUBY
end
end

context 'when `be_a_kind_of` is used' do
it 'does not register an offense' do
expect_no_offenses(<<~RUBY)
expect(a).to be_a_kind_of(b)
RUBY
end
end

context 'when `be_a` is used' do
it 'registers and corrects an offense' do
expect_offense(<<~RUBY)
expect(a).to be_a(b)
^^^^ Prefer `be_kind_of` over `be_a`.
RUBY

expect_correction(<<~RUBY)
expect(a).to be_kind_of(b)
RUBY
end
end

context 'when `be_an` is used' do
it 'registers and corrects an offense' do
expect_offense(<<~RUBY)
expect(a).to be_an(b)
^^^^^ Prefer `be_kind_of` over `be_an`.
RUBY

expect_correction(<<~RUBY)
expect(a).to be_kind_of(b)
RUBY
end
end
end
end