Skip to content

Commit

Permalink
Add new Performance/ArraySemiInfiniteRangeSlice cop
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima committed Sep 29, 2020
1 parent 63c1efb commit 844c097
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## master (unreleased)

### New features

* [#175](https://github.com/rubocop-hq/rubocop-performance/pull/175): Add new `Performance/ArraySemiInfiniteRangeSlice` cop. ([@fatkodima][])

### Changes

* [#170](https://github.com/rubocop-hq/rubocop-performance/pull/170): Extend `Performance/Sum` to register an offense for `map { ... }.sum`. ([@eugeneius][])
Expand Down
5 changes: 5 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ Performance/AncestorsInclude:
Safe: false
VersionAdded: '1.7'

Performance/ArraySemiInfiniteRangeSlice:
Description: 'Identifies places where slicing arrays with semi-infinite ranges can be replaced by `Array#take` and `Array#drop`.'
Enabled: pending
VersionAdded: '1.9'

Performance/BigDecimalWithNumericArgument:
Description: 'Convert numeric argument to string before passing to BigDecimal.'
Enabled: 'pending'
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 @@ -13,6 +13,7 @@ Performance cops optimization analysis for your projects.
=== Department xref:cops_performance.adoc[Performance]

* xref:cops_performance.adoc#performanceancestorsinclude[Performance/AncestorsInclude]
* xref:cops_performance.adoc#performancearraysemiinfiniterangeslice[Performance/ArraySemiInfiniteRangeSlice]
* xref:cops_performance.adoc#performancebigdecimalwithnumericargument[Performance/BigDecimalWithNumericArgument]
* xref:cops_performance.adoc#performancebindcall[Performance/BindCall]
* xref:cops_performance.adoc#performancecaller[Performance/Caller]
Expand Down
36 changes: 36 additions & 0 deletions docs/modules/ROOT/pages/cops_performance.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,42 @@ A <= B

* https://github.com/JuanitoFatas/fast-ruby#ancestorsinclude-vs--code

== Performance/ArraySemiInfiniteRangeSlice

NOTE: Required Ruby version: 2.7

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

| Pending
| Yes
| Yes
| 1.9
| -
|===

This cop identifies places where slicing arrays with semi-infinite ranges
can be replaced by `Array#take` and `Array#drop`.

=== Examples

[source,ruby]
----
# bad
# array[..2]
# array[...2]
# array[2..]
# array[2...]
# array.slice(..2)
# good
array.take(3)
array.take(2)
array.drop(2)
array.drop(2)
array.take(3)
----

== Performance/BigDecimalWithNumericArgument

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

module RuboCop
module Cop
module Performance
# This cop identifies places where slicing arrays with semi-infinite ranges
# can be replaced by `Array#take` and `Array#drop`.
#
# @example
# # bad
# # array[..2]
# # array[...2]
# # array[2..]
# # array[2...]
# # array.slice(..2)
#
# # good
# array.take(3)
# array.take(2)
# array.drop(2)
# array.drop(2)
# array.take(3)
#
class ArraySemiInfiniteRangeSlice < Base
include RangeHelp
extend AutoCorrector
extend TargetRubyVersion

minimum_target_ruby_version 2.7

MSG = 'Use `%<prefer>s` instead of `%<current>s` with semi-infinite range.'

SLICE_METHODS = Set[:[], :slice].freeze
RESTRICT_ON_SEND = SLICE_METHODS

def_node_matcher :endless_range_slice?, <<~PATTERN
(send $_ $%SLICE_METHODS $#endless_range?)
PATTERN

def_node_matcher :endless_range?, <<~PATTERN
{
({irange erange} nil? (int positive?))
({irange erange} (int positive?) nil?)
}
PATTERN

def on_send(node)
endless_range_slice?(node) do |receiver, method_name, range_node|
prefer = range_node.begin ? :drop : :take
message = format(MSG, prefer: prefer, current: method_name)

add_offense(node, message: message) do |corrector|
corrector.replace(node, correction(receiver, range_node))
end
end
end

private

def correction(receiver, range_node)
method_call = if range_node.begin
"drop(#{range_node.begin.value})"
elsif range_node.irange_type?
"take(#{range_node.end.value + 1})"
else
"take(#{range_node.end.value})"
end

"#{receiver.source}.#{method_call}"
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/performance_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
require_relative 'mixin/sort_block'

require_relative 'performance/ancestors_include'
require_relative 'performance/array_semi_infinite_range_slice'
require_relative 'performance/big_decimal_with_numeric_argument'
require_relative 'performance/bind_call'
require_relative 'performance/caller'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Performance::ArraySemiInfiniteRangeSlice, :config do
subject(:cop) { described_class.new(config) }

# TODO: The following is no longer required when RuboCop 0.78 or lower support will be dropped.
# https://github.com/rubocop-hq/rubocop/pull/7605
let(:ruby_version) { 2.7 }

context 'TargetRubyVersion >= 2.7', :ruby27 do
it 'registers an offense and corrects when using `[]` with beginless range' do
expect_offense(<<~RUBY)
array[..2]
^^^^^^^^^^ Use `take` instead of `[]` with semi-infinite range.
array[...2]
^^^^^^^^^^^ Use `take` instead of `[]` with semi-infinite range.
RUBY

expect_correction(<<~RUBY)
array.take(3)
array.take(2)
RUBY
end

it 'registers an offense and corrects when using `[]` with endless range' do
expect_offense(<<~RUBY)
array[2..]
^^^^^^^^^^ Use `drop` instead of `[]` with semi-infinite range.
array[2...]
^^^^^^^^^^^ Use `drop` instead of `[]` with semi-infinite range.
RUBY

expect_correction(<<~RUBY)
array.drop(2)
array.drop(2)
RUBY
end

it 'registers an offense and corrects when using `slice` with semi-infinite ranges' do
expect_offense(<<~RUBY)
array.slice(2..)
^^^^^^^^^^^^^^^^ Use `drop` instead of `slice` with semi-infinite range.
array.slice(..2)
^^^^^^^^^^^^^^^^ Use `take` instead of `slice` with semi-infinite range.
RUBY

expect_correction(<<~RUBY)
array.drop(2)
array.take(3)
RUBY
end

it 'does not register an offense when using `[]` with full range' do
expect_no_offenses(<<~RUBY)
array[0..2]
RUBY
end

it 'does not register an offense when using `[]` with semi-infinite range with non literal' do
expect_no_offenses(<<~RUBY)
array[..index]
array[index..]
RUBY
end

it 'does not register an offense when using `[]` with semi-infinite range with negative int' do
expect_no_offenses(<<~RUBY)
array[..-2]
array[-2..]
RUBY
end
end
end

0 comments on commit 844c097

Please sign in to comment.