Skip to content

Commit

Permalink
Add new Style/CollectionCompact cop
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima authored and bbatsov committed Nov 4, 2020
1 parent 50c0607 commit 7de9f00
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog/new_collection_compact_cop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* [#8566](https://github.com/rubocop-hq/rubocop/issues/8566): Add new `Style/CollectionCompact` cop. ([@fatkodima][])
5 changes: 5 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2801,6 +2801,11 @@ Style/ClassVars:
Enabled: true
VersionAdded: '0.13'

Style/CollectionCompact:
Description: 'Use `{Array,Hash}#{compact,compact!}` instead of custom logic to reject nils.'
Enabled: pending
VersionAdded: '1.2'

# Align with the style guide.
Style/CollectionMethods:
Description: 'Preferred collection methods.'
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 @@ -361,6 +361,7 @@ In the following section you find all available cops:
* xref:cops_style.adoc#styleclassmethods[Style/ClassMethods]
* xref:cops_style.adoc#styleclassmethodsdefinitions[Style/ClassMethodsDefinitions]
* xref:cops_style.adoc#styleclassvars[Style/ClassVars]
* xref:cops_style.adoc#stylecollectioncompact[Style/CollectionCompact]
* xref:cops_style.adoc#stylecollectionmethods[Style/CollectionMethods]
* xref:cops_style.adoc#stylecolonmethodcall[Style/ColonMethodCall]
* xref:cops_style.adoc#stylecolonmethoddefinition[Style/ColonMethodDefinition]
Expand Down
34 changes: 34 additions & 0 deletions docs/modules/ROOT/pages/cops_style.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,40 @@ end

* https://rubystyle.guide#no-class-vars

== Style/CollectionCompact

|===
| Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged

| Pending
| Yes
| Yes
| 1.2
| -
|===

This cop checks for places where custom logic on rejection nils from arrays
and hashes can be replaced with `{Array,Hash}#{compact,compact!}`.

=== Examples

[source,ruby]
----
# bad
array.reject { |e| e.nil? }
array.select { |e| !e.nil? }
# good
array.compact
# bad
hash.reject! { |k, v| v.nil? }
hash.select! { |k, v| !v.nil? }
# good
hash.compact!
----

== Style/CollectionMethods

|===
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,7 @@
require_relative 'rubocop/cop/style/class_methods'
require_relative 'rubocop/cop/style/class_methods_definitions'
require_relative 'rubocop/cop/style/class_vars'
require_relative 'rubocop/cop/style/collection_compact'
require_relative 'rubocop/cop/style/collection_methods'
require_relative 'rubocop/cop/style/colon_method_call'
require_relative 'rubocop/cop/style/colon_method_definition'
Expand Down
85 changes: 85 additions & 0 deletions lib/rubocop/cop/style/collection_compact.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# This cop checks for places where custom logic on rejection nils from arrays
# and hashes can be replaced with `{Array,Hash}#{compact,compact!}`.
#
# @example
# # bad
# array.reject { |e| e.nil? }
# array.select { |e| !e.nil? }
#
# # good
# array.compact
#
# # bad
# hash.reject! { |k, v| v.nil? }
# hash.select! { |k, v| !v.nil? }
#
# # good
# hash.compact!
#
class CollectionCompact < Base
include RangeHelp
extend AutoCorrector

MSG = 'Use `%<good>s` instead of `%<bad>s`.'

RESTRICT_ON_SEND = %i[reject reject! select select!].freeze

def_node_matcher :reject_method?, <<~PATTERN
(block
(send
_ ${:reject :reject!})
$(args ...)
(send
$(lvar _) :nil?))
PATTERN

def_node_matcher :select_method?, <<~PATTERN
(block
(send
_ ${:select :select!})
$(args ...)
(send
(send
$(lvar _) :nil?) :!))
PATTERN

def on_send(node)
block_node = node.parent
return unless block_node&.block_type?

return unless (method_name, args, receiver =
reject_method?(block_node) || select_method?(block_node))

return unless args.last.source == receiver.source

range = offense_range(node, block_node)
good = good_method_name(method_name)
message = format(MSG, good: good, bad: range.source)

add_offense(range, message: message) do |corrector|
corrector.replace(range, good)
end
end

private

def good_method_name(method_name)
if method_name.to_s.end_with?('!')
'compact!'
else
'compact'
end
end

def offense_range(send_node, block_node)
range_between(send_node.loc.selector.begin_pos, block_node.loc.end.end_pos)
end
end
end
end
end
71 changes: 71 additions & 0 deletions spec/rubocop/cop/style/collection_compact_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Style::CollectionCompact do
subject(:cop) { described_class.new }

it 'registers an offense and corrects when using `reject` on array to reject nils' do
expect_offense(<<~RUBY)
array.reject { |e| e.nil? }
^^^^^^^^^^^^^^^^^^^^^ Use `compact` instead of `reject { |e| e.nil? }`.
array.reject! { |e| e.nil? }
^^^^^^^^^^^^^^^^^^^^^^ Use `compact!` instead of `reject! { |e| e.nil? }`.
RUBY

expect_correction(<<~RUBY)
array.compact
array.compact!
RUBY
end

it 'registers an offense and corrects when using `reject` on hash to reject nils' do
expect_offense(<<~RUBY)
hash.reject { |k, v| v.nil? }
^^^^^^^^^^^^^^^^^^^^^^^^ Use `compact` instead of `reject { |k, v| v.nil? }`.
hash.reject! { |k, v| v.nil? }
^^^^^^^^^^^^^^^^^^^^^^^^^ Use `compact!` instead of `reject! { |k, v| v.nil? }`.
RUBY

expect_correction(<<~RUBY)
hash.compact
hash.compact!
RUBY
end

it 'registers an offense and corrects when using `select/select!` to reject nils' do
expect_offense(<<~RUBY)
array.select { |e| !e.nil? }
^^^^^^^^^^^^^^^^^^^^^^ Use `compact` instead of `select { |e| !e.nil? }`.
hash.select! { |k, v| !v.nil? }
^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `compact!` instead of `select! { |k, v| !v.nil? }`.
RUBY

expect_correction(<<~RUBY)
array.compact
hash.compact!
RUBY
end

it 'registers an offense and corrects when using `reject` without a receiver to reject nils' do
expect_offense(<<~RUBY)
reject { |e| e.nil? }
^^^^^^^^^^^^^^^^^^^^^ Use `compact` instead of `reject { |e| e.nil? }`.
RUBY

expect_correction(<<~RUBY)
compact
RUBY
end

it 'does not register an offense when using `reject` to not to rejecting nils' do
expect_no_offenses(<<~RUBY)
array.reject { |e| e.odd? }
RUBY
end

it 'does not register an offense when using `compact/compact!`' do
expect_no_offenses(<<~RUBY)
array.compact
array.compact!
RUBY
end
end

0 comments on commit 7de9f00

Please sign in to comment.