Skip to content

Commit

Permalink
[Fix rubocop#1155] Add new Rails/AssertNotPredicate
Browse files Browse the repository at this point in the history
This converts `assert_not(obj.one?)` into `assert_not_predicate(obj, :one?)`
These methods are rails-specific and not present by default in minitest.

For users that prefer the `refute` method calls, `Rails/RefuteMethods` will correct this if configured that way.

Both the code and tests are pretty much a one-to-one copy from rubocop-minitest.`RefutePredicate`
  • Loading branch information
Earlopain committed Mar 22, 2024
1 parent 650e783 commit c86dbf7
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_rails_assert_not_predicate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#1155](https://github.com/rubocop/rubocop-rails/issues/1155): Add new `Rails/AssertNotPredicate` to convert `assert_not(obj.foo?)` into `assert_not_predicate(obj, :foo?)`. ([@earlopain][])
7 changes: 7 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,13 @@ Rails/AssertNot:
Include:
- '**/test/**/*'

Rails/AssertNotPredicate:
Description: 'Prefer `assert_not_predicate` instead of `assert_not` with predicate method.'
Enabled: pending
VersionAdded: '<<next>>'
Include:
- '**/test/**/*'

Rails/AttributeDefaultBlockValue:
Description: 'Pass method call in block for attribute option `default`.'
Enabled: pending
Expand Down
74 changes: 74 additions & 0 deletions lib/rubocop/cop/rails/assert_not_predicate.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Rails
# Prefer assert_not_predicate(obj, :foo?) over assert_not(obj.foo?)
#
# @example
# # bad
# assert_not(obj.one?)
# assert_not(obj.one?, 'message')
#
# # good
# assert_not_predicate(obj, :one?)
# assert_not_predicate(obj, :one?, 'message')
#
class AssertNotPredicate < Base
# NOTE: Code lifted from rubocop-minitest `PredicateAssertionHandleable`
extend AutoCorrector

MSG = 'Prefer using `%<assertion_type>s_predicate(%<new_arguments>s)`.'
RESTRICT_ON_SEND = [:assert_not].freeze

def assertion_type
:assert_not
end

def on_send(node)
return unless node.first_argument
return if node.first_argument.block_type? || node.first_argument.numblock_type?
return unless predicate_method?(node.first_argument)
return unless node.first_argument.arguments.count.zero?

add_offense(node, message: offense_message(node.arguments)) do |corrector|
autocorrect(corrector, node, node.arguments)
end
end

def autocorrect(corrector, node, arguments)
corrector.replace(node.loc.selector, "#{assertion_type}_predicate")

new_arguments = new_arguments(arguments).join(', ')

corrector.replace(node.first_argument, new_arguments)
end

private

def predicate_method?(first_argument)
first_argument.respond_to?(:predicate_method?) && first_argument.predicate_method?
end

def offense_message(arguments)
message_argument = arguments.last if arguments.first != arguments.last

new_arguments = [new_arguments(arguments), message_argument&.source].flatten.compact.join(', ')

format(MSG, assertion_type: assertion_type, new_arguments: new_arguments)
end

def new_arguments(arguments)
receiver = correct_receiver(arguments.first.receiver)
method_name = arguments.first.method_name

[receiver, ":#{method_name}"]
end

def correct_receiver(receiver)
receiver ? receiver.source : 'self'
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rails_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
require_relative 'rails/application_record'
require_relative 'rails/arel_star'
require_relative 'rails/assert_not'
require_relative 'rails/assert_not_predicate'
require_relative 'rails/attribute_default_block_value'
require_relative 'rails/belongs_to'
require_relative 'rails/blank'
Expand Down
88 changes: 88 additions & 0 deletions spec/rubocop/cop/rails/assert_not_predicate_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Rails::AssertNotPredicate, :config do
it 'registers an offense when using assert_not with predicate method' do
expect_offense(<<~RUBY)
assert_not(obj.one?)
^^^^^^^^^^^^^^^^^^^^ Prefer using `assert_not_predicate(obj, :one?)`.
RUBY

expect_correction(<<~RUBY)
assert_not_predicate(obj, :one?)
RUBY
end

it 'registers an offense when using assert_not with predicate method and message' do
expect_offense(<<~RUBY)
assert_not(obj.one?, 'message')
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `assert_not_predicate(obj, :one?, 'message')`.
RUBY

expect_correction(<<~RUBY)
assert_not_predicate(obj, :one?, 'message')
RUBY
end

it 'registers an offense when using assert_not with predicate method and heredoc message' do
expect_offense(<<~RUBY)
assert_not(obj.one?, <<~MESSAGE)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer using `assert_not_predicate(obj, :one?, <<~MESSAGE)`.
message
MESSAGE
RUBY

expect_correction(<<~RUBY)
assert_not_predicate(obj, :one?, <<~MESSAGE)
message
MESSAGE
RUBY
end

it 'registers and offense when using assert_not with receiver omitted predicate method' do
expect_offense(<<~RUBY)
assert_not(one?)
^^^^^^^^^^^^^^^^ Prefer using `assert_not_predicate(self, :one?)`.
RUBY

expect_correction(<<~RUBY)
assert_not_predicate(self, :one?)
RUBY
end

it 'registers no offense when using assert_not_predicate method' do
expect_no_offenses(<<~RUBY)
assert_not_predicate(obj, :one?)
RUBY
end

it 'registers no offense when using assert_not with non predicate method' do
expect_no_offenses(<<~RUBY)
assert_not(obj.do_something)
RUBY
end

it 'registers no offense when using assert_not with local variable' do
expect_no_offenses(<<~RUBY)
obj = create_obj
assert_not(obj)
RUBY
end

it 'registers no offense when using assert_not_predicate with predicate method and arguments' do
expect_no_offenses(<<~RUBY)
assert_not(obj.foo?(arg))
RUBY
end

it 'registers no offense when using assert_not with predicate method and numbered arguments' do
expect_no_offenses(<<~RUBY)
assert_not([1, 2, 3].any? { some_filter_function _1 })
RUBY
end

it 'raises no error when using assert_not with block' do
expect_no_offenses(<<~RUBY)
assert_not { false }
RUBY
end
end

0 comments on commit c86dbf7

Please sign in to comment.