Skip to content

Commit

Permalink
Add new Style/MinMax cop
Browse files Browse the repository at this point in the history
This cop checks for potential uses of `Enumberable#minmax`, e.g.:

```
[foo.min, foo.max]
```

can be changed to:

```
foo.minmax
```
  • Loading branch information
Drenmi committed Sep 11, 2017
1 parent 4fc5e2d commit f832af5
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
* [#4702](https://github.com/bbatsov/rubocop/pull/4702): Add new `Lint/UriEscapeUnescape` cop. ([@koic][])
* [#4696](https://github.com/bbatsov/rubocop/pull/4696): Add new `Performance/UriDefaultParser` cop. ([@koic][])
* [#4694](https://github.com/bbatsov/rubocop/pull/4694): Add new `Lint/UriRegexp` cop. ([@koic][])
* Add new `Style/MinMax` cop. ([@drenmi][])

### Bug fixes

Expand Down
6 changes: 6 additions & 0 deletions config/enabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,12 @@ Style/MethodMissing:
StyleGuide: '#no-method-missing'
Enabled: true

Style/MinMax:
Description: >-
Use `Enumerable#minmax` instead of `Enumerable#min`
and `Enumerable#max` in conjunction.'
Enabled: true

Style/MixinGrouping:
Description: 'Checks for grouping of mixins in `class` and `module` bodies.'
StyleGuide: '#mixin-grouping'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@
require 'rubocop/cop/style/method_called_on_do_end_block'
require 'rubocop/cop/style/method_def_parentheses'
require 'rubocop/cop/style/method_missing'
require 'rubocop/cop/style/min_max'
require 'rubocop/cop/style/missing_else'
require 'rubocop/cop/style/mixin_grouping'
require 'rubocop/cop/style/module_function'
Expand Down
67 changes: 67 additions & 0 deletions lib/rubocop/cop/style/min_max.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Style
# This cop checks for potential uses of `Enumerable#minmax`.
#
# @example
#
# @bad
# bar = [foo.min, foo.max]
# return foo.min, foo.max
#
# @good
# bar = foo.minmax
# return foo.minmax
class MinMax < Cop
MSG = 'Use `%<receiver>s.minmax` instead of `%<offender>s`.'.freeze

def on_array(node)
min_max_candidate(node) do |receiver|
offender = offending_range(node)

add_offense(node, offender, message(offender, receiver))
end
end
alias on_return on_array

private

def_node_matcher :min_max_candidate, <<-PATTERN
({array return} (send $_receiver :min) (send $_receiver :max))
PATTERN

def message(offender, receiver)
format(MSG, offender: offender.source,
receiver: receiver.source)
end

def autocorrect(node)
receiver = node.children.first.receiver

lambda do |corrector|
corrector.replace(offending_range(node),
"#{receiver.source}.minmax")
end
end

def offending_range(node)
case node.type
when :return
argument_range(node)
else
node.loc.expression
end
end

def argument_range(node)
first_argument_range = node.children.first.loc.expression
last_argument_range = node.children.last.loc.expression

first_argument_range.join(last_argument_range)
end
end
end
end
end
1 change: 1 addition & 0 deletions manual/cops.md
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,7 @@ In the following section you find all available cops:
* [Style/MethodCalledOnDoEndBlock](cops_style.md#stylemethodcalledondoendblock)
* [Style/MethodDefParentheses](cops_style.md#stylemethoddefparentheses)
* [Style/MethodMissing](cops_style.md#stylemethodmissing)
* [Style/MinMax](cops_style.md#styleminmax)
* [Style/MissingElse](cops_style.md#stylemissingelse)
* [Style/MixinGrouping](cops_style.md#stylemixingrouping)
* [Style/ModuleFunction](cops_style.md#stylemodulefunction)
Expand Down
20 changes: 20 additions & 0 deletions manual/cops_style.md
Original file line number Diff line number Diff line change
Expand Up @@ -1915,6 +1915,26 @@ end

* [https://github.com/bbatsov/ruby-style-guide#no-method-missing](https://github.com/bbatsov/ruby-style-guide#no-method-missing)

## Style/MinMax

Enabled by default | Supports autocorrection
--- | ---
Enabled | Yes

This cop checks for potential uses of `Enumerable#minmax`.

### Example

```ruby
# bad
bar = [foo.min, foo.max]
return foo.min, foo.max

# good
bar = foo.minmax
return foo.minmax
```

## Style/MissingElse

Enabled by default | Supports autocorrection
Expand Down
100 changes: 100 additions & 0 deletions spec/rubocop/cop/style/min_max_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# frozen_string_literal: true

describe RuboCop::Cop::Style::MinMax, :config do
subject(:cop) { described_class.new(config) }

context 'with an array literal containing calls to `#min` and `#max`' do
context 'when the expression stands alone' do
it 'registers an offense if the receivers match' do
expect_offense(<<-RUBY.strip_indent)
[foo.min, foo.max]
^^^^^^^^^^^^^^^^^^ Use `foo.minmax` instead of `[foo.min, foo.max]`.
RUBY
end

it 'does not register an offense if the receivers do not match' do
expect_no_offenses(<<-RUBY.strip_indent)
[foo.min, bar.max]
RUBY
end

it 'does not register an offense if there are additional elements' do
expect_no_offenses(<<-RUBY.strip_indent)
[foo.min, foo.baz, foo.max]
RUBY
end

it 'auto-corrects an offense to use `#minmax`' do
corrected = autocorrect_source(<<-RUBY.strip_indent)
[foo.bar.min, foo.bar.max]
RUBY

expect(corrected).to eq(<<-RUBY.strip_indent)
foo.bar.minmax
RUBY
end
end

context 'when the expression is used in a parallel assignment' do
it 'registers an offense if the receivers match' do
expect_offense(<<-RUBY.strip_indent)
bar = foo.min, foo.max
^^^^^^^^^^^^^^^^ Use `foo.minmax` instead of `foo.min, foo.max`.
RUBY
end

it 'does not register an offense if the receivers do not match' do
expect_no_offenses(<<-RUBY.strip_indent)
baz = foo.min, bar.max
RUBY
end

it 'does not register an offense if there are additional elements' do
expect_no_offenses(<<-RUBY.strip_indent)
bar = foo.min, foo.baz, foo.max
RUBY
end

it 'auto-corrects an offense to use `#minmax`' do
corrected = autocorrect_source(<<-RUBY.strip_indent)
baz = foo.bar.min, foo.bar.max
RUBY

expect(corrected).to eq(<<-RUBY.strip_indent)
baz = foo.bar.minmax
RUBY
end
end

context 'when the expression is used as a return value' do
it 'registers an offense if the receivers match' do
expect_offense(<<-RUBY.strip_indent)
return foo.min, foo.max
^^^^^^^^^^^^^^^^ Use `foo.minmax` instead of `foo.min, foo.max`.
RUBY
end

it 'does not register an offense if the receivers do not match' do
expect_no_offenses(<<-RUBY.strip_indent)
return foo.min, bar.max
RUBY
end

it 'does not register an offense if there are additional elements' do
expect_no_offenses(<<-RUBY.strip_indent)
return foo.min, foo.baz, foo.max
RUBY
end

it 'auto-corrects an offense to use `#minmax`' do
corrected = autocorrect_source(<<-RUBY.strip_indent)
return foo.bar.min, foo.bar.max
RUBY

expect(corrected).to eq(<<-RUBY.strip_indent)
return foo.bar.minmax
RUBY
end
end
end
end

0 comments on commit f832af5

Please sign in to comment.