Skip to content

Commit

Permalink
Add AllowOnSelfClass option to Style/CaseEquality
Browse files Browse the repository at this point in the history
`Style/CaseEquality`'s `AllowOnConstant` option allows the use of constants, to
allow for case equality with classes and modules. `self.class` is a special
case also referring to a class, and may be required in cases where the other
object might be a `BasicObject`.
  • Loading branch information
sambostock authored and bbatsov committed Aug 29, 2022
1 parent 2e3418b commit 3360eac
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 10 deletions.
1 change: 1 addition & 0 deletions changelog/new_add_allowonselfclass_option_to.md
@@ -0,0 +1 @@
* [#10931](https://github.com/rubocop/rubocop/pull/10931): Add `AllowOnSelfClass` option to `Style/CaseEquality`. ([@sambostock][])
9 changes: 9 additions & 0 deletions config/default.yml
Expand Up @@ -3188,6 +3188,15 @@ Style/CaseEquality:
# # good
# String === "string"
AllowOnConstant: false
# If `AllowOnSelfClass` option is enabled, the cop will ignore violations when the receiver of
# the case equality operator is `self.class`.
#
# # bad
# some_class === object
#
# # good
# self.class === object
AllowOnSelfClass: false

Style/CaseLikeIf:
Description: 'Identifies places where `if-elsif` constructions can be replaced with `case-when`.'
Expand Down
50 changes: 40 additions & 10 deletions lib/rubocop/cop/style/case_equality.rb
Expand Up @@ -7,6 +7,9 @@ module Style
#
# If `AllowOnConstant` option is enabled, the cop will ignore violations when the receiver of
# the case equality operator is a constant.

# If `AllowOnSelfClass` option is enabled, the cop will ignore violations when the receiver of
# the case equality operator is `self.class`. Note intermediate variables are not accepted.
#
# @example
# # bad
Expand All @@ -26,14 +29,25 @@ module Style
# # good
# Array === something
#
# @example AllowOnSelfClass: false (default)
# # bad
# self.class === something
#
# @example AllowOnSelfClass: true
# # good
# self.class === something
#
class CaseEquality < Base
extend AutoCorrector

MSG = 'Avoid the use of the case equality operator `===`.'
RESTRICT_ON_SEND = %i[===].freeze

# @!method case_equality?(node)
def_node_matcher :case_equality?, '(send $#const? :=== $_)'
def_node_matcher :case_equality?, '(send $#offending_receiver? :=== $_)'

# @!method self_class?(node)
def_node_matcher :self_class?, '(send (self) :class)'

def on_send(node)
case_equality?(node) do |lhs, rhs|
Expand All @@ -48,12 +62,11 @@ def on_send(node)

private

def const?(node)
if cop_config.fetch('AllowOnConstant', false)
!node&.const_type?
else
true
end
def offending_receiver?(node)
return false if node&.const_type? && cop_config.fetch('AllowOnConstant', false)
return false if self_class?(node) && cop_config.fetch('AllowOnSelfClass', false)

true
end

def replacement(lhs, rhs)
Expand All @@ -66,12 +79,29 @@ def replacement(lhs, rhs)
#
# So here is noop.
when :begin
child = lhs.children.first
"#{lhs.source}.include?(#{rhs.source})" if child&.range_type?
begin_replacement(lhs, rhs)
when :const
"#{rhs.source}.is_a?(#{lhs.source})"
const_replacement(lhs, rhs)
when :send
send_replacement(lhs, rhs)
end
end

def begin_replacement(lhs, rhs)
return unless lhs.children.first&.range_type?

"#{lhs.source}.include?(#{rhs.source})"
end

def const_replacement(lhs, rhs)
"#{rhs.source}.is_a?(#{lhs.source})"
end

def send_replacement(lhs, rhs)
return unless self_class?(lhs)

"#{rhs.source}.is_a?(#{lhs.source})"
end
end
end
end
Expand Down
38 changes: 38 additions & 0 deletions spec/rubocop/cop/style/case_equality_spec.rb
Expand Up @@ -73,4 +73,42 @@

include_examples 'offenses'
end

context 'when AllowOnSelfClass is false' do
let(:cop_config) { { 'AllowOnSelfClass' => false } }

it 'registers an offense and corrects for === when the receiver is self.class' do
expect_offense(<<~RUBY)
self.class === var
^^^ Avoid the use of the case equality operator `===`.
RUBY

expect_correction(<<~RUBY)
var.is_a?(self.class)
RUBY
end

include_examples 'offenses'
end

context 'when AllowOnSelfClass is true' do
let(:cop_config) { { 'AllowOnSelfClass' => true } }

it 'does not register an offense for === when the receiver is self.class' do
expect_no_offenses(<<~RUBY)
self.class === var
RUBY
end

it 'registers an offense and corrects for === when the receiver is self.klass' do
expect_offense(<<~RUBY)
self.klass === var
^^^ Avoid the use of the case equality operator `===`.
RUBY

expect_no_corrections
end

include_examples 'offenses'
end
end

0 comments on commit 3360eac

Please sign in to comment.