Skip to content

Commit

Permalink
Add RSpec/ClassCheck cop
Browse files Browse the repository at this point in the history
  • Loading branch information
r7kamura committed Aug 19, 2022
1 parent 740ce57 commit 0b10d67
Show file tree
Hide file tree
Showing 8 changed files with 265 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ RSpec:
- expect_no_offenses
- expect_offense

RSpec/ClassCheck:
Enabled: true

RSpec/ExampleLength:
CountAsOne:
- heredoc
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
* Fix a false positive for `RSpec/Capybara/SpecificMatcher` when may not have a `href` by `have_link`. ([@ydah][])
* Add `NegatedMatcher` configuration option to `RSpec/ChangeByZero`. ([@ydah][])
* Add new `RSpec/Capybara/SpecificFinders` cop. ([@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 @@ -197,6 +197,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
88 changes: 88 additions & 0 deletions lib/rubocop/cop/rspec/class_check.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# 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

RESTRICT_ON_SEND = %i[
be_a
be_a_kind_of
be_an
be_kind_of
].freeze

def on_send(node)
return if preferred_method_name?(node.method_name)

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, style)
end

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

def preferred_method_name?(method_name)
preferred_method_names.include?(method_name)
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 enforced style is be_a and 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 enforced style is be_a and 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 enforced style is be_a and 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 enforced style is be_a and 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

context 'when enforced style is be_kind_of and be_kind_of is used' do
let(:cop_config) do
{ 'EnforcedStyle' => 'be_kind_of' }
end

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

context 'when enforced style is be_kind_of and be_a_kind_of is used' do
let(:cop_config) do
{ 'EnforcedStyle' => 'be_kind_of' }
end

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

context 'when enforced style is be_kind_of and be_a is used' do
let(:cop_config) do
{ 'EnforcedStyle' => 'be_kind_of' }
end

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 enforced style is be_kind_of and be_an is used' do
let(:cop_config) do
{ 'EnforcedStyle' => 'be_kind_of' }
end

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

0 comments on commit 0b10d67

Please sign in to comment.