Skip to content

Commit e0fdb0b

Browse files
Drenmibbatsov
authored andcommitted
Add new Style/MinMax cop
This cop checks for potential uses of `Enumberable#minmax`, e.g.: ``` [foo.min, foo.max] ``` can be changed to: ``` foo.minmax ```
1 parent 4fc5e2d commit e0fdb0b

File tree

7 files changed

+196
-0
lines changed

7 files changed

+196
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
* [#4702](https://github.com/bbatsov/rubocop/pull/4702): Add new `Lint/UriEscapeUnescape` cop. ([@koic][])
2929
* [#4696](https://github.com/bbatsov/rubocop/pull/4696): Add new `Performance/UriDefaultParser` cop. ([@koic][])
3030
* [#4694](https://github.com/bbatsov/rubocop/pull/4694): Add new `Lint/UriRegexp` cop. ([@koic][])
31+
* Add new `Style/MinMax` cop. ([@drenmi][])
3132

3233
### Bug fixes
3334

config/enabled.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,12 @@ Style/MethodMissing:
687687
StyleGuide: '#no-method-missing'
688688
Enabled: true
689689

690+
Style/MinMax:
691+
Description: >-
692+
Use `Enumerable#minmax` instead of `Enumerable#min`
693+
and `Enumerable#max` in conjunction.'
694+
Enabled: true
695+
690696
Style/MixinGrouping:
691697
Description: 'Checks for grouping of mixins in `class` and `module` bodies.'
692698
StyleGuide: '#mixin-grouping'

lib/rubocop.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,7 @@
391391
require 'rubocop/cop/style/method_called_on_do_end_block'
392392
require 'rubocop/cop/style/method_def_parentheses'
393393
require 'rubocop/cop/style/method_missing'
394+
require 'rubocop/cop/style/min_max'
394395
require 'rubocop/cop/style/missing_else'
395396
require 'rubocop/cop/style/mixin_grouping'
396397
require 'rubocop/cop/style/module_function'

lib/rubocop/cop/style/min_max.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# frozen_string_literal: true
2+
3+
module RuboCop
4+
module Cop
5+
module Style
6+
# This cop checks for potential uses of `Enumerable#minmax`.
7+
#
8+
# @example
9+
#
10+
# @bad
11+
# bar = [foo.min, foo.max]
12+
# return foo.min, foo.max
13+
#
14+
# @good
15+
# bar = foo.minmax
16+
# return foo.minmax
17+
class MinMax < Cop
18+
MSG = 'Use `%<receiver>s.minmax` instead of `%<offender>s`.'.freeze
19+
20+
def on_array(node)
21+
min_max_candidate(node) do |receiver|
22+
offender = offending_range(node)
23+
24+
add_offense(node, offender, message(offender, receiver))
25+
end
26+
end
27+
alias on_return on_array
28+
29+
private
30+
31+
def_node_matcher :min_max_candidate, <<-PATTERN
32+
({array return} (send $_receiver :min) (send $_receiver :max))
33+
PATTERN
34+
35+
def message(offender, receiver)
36+
format(MSG, offender: offender.source,
37+
receiver: receiver.source)
38+
end
39+
40+
def autocorrect(node)
41+
receiver = node.children.first.receiver
42+
43+
lambda do |corrector|
44+
corrector.replace(offending_range(node),
45+
"#{receiver.source}.minmax")
46+
end
47+
end
48+
49+
def offending_range(node)
50+
case node.type
51+
when :return
52+
argument_range(node)
53+
else
54+
node.loc.expression
55+
end
56+
end
57+
58+
def argument_range(node)
59+
first_argument_range = node.children.first.loc.expression
60+
last_argument_range = node.children.last.loc.expression
61+
62+
first_argument_range.join(last_argument_range)
63+
end
64+
end
65+
end
66+
end
67+
end

manual/cops.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ In the following section you find all available cops:
399399
* [Style/MethodCalledOnDoEndBlock](cops_style.md#stylemethodcalledondoendblock)
400400
* [Style/MethodDefParentheses](cops_style.md#stylemethoddefparentheses)
401401
* [Style/MethodMissing](cops_style.md#stylemethodmissing)
402+
* [Style/MinMax](cops_style.md#styleminmax)
402403
* [Style/MissingElse](cops_style.md#stylemissingelse)
403404
* [Style/MixinGrouping](cops_style.md#stylemixingrouping)
404405
* [Style/ModuleFunction](cops_style.md#stylemodulefunction)

manual/cops_style.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1915,6 +1915,26 @@ end
19151915

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

1918+
## Style/MinMax
1919+
1920+
Enabled by default | Supports autocorrection
1921+
--- | ---
1922+
Enabled | Yes
1923+
1924+
This cop checks for potential uses of `Enumerable#minmax`.
1925+
1926+
### Example
1927+
1928+
```ruby
1929+
# bad
1930+
bar = [foo.min, foo.max]
1931+
return foo.min, foo.max
1932+
1933+
# good
1934+
bar = foo.minmax
1935+
return foo.minmax
1936+
```
1937+
19181938
## Style/MissingElse
19191939

19201940
Enabled by default | Supports autocorrection
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
# frozen_string_literal: true
2+
3+
describe RuboCop::Cop::Style::MinMax, :config do
4+
subject(:cop) { described_class.new(config) }
5+
6+
context 'with an array literal containing calls to `#min` and `#max`' do
7+
context 'when the expression stands alone' do
8+
it 'registers an offense if the receivers match' do
9+
expect_offense(<<-RUBY.strip_indent)
10+
[foo.min, foo.max]
11+
^^^^^^^^^^^^^^^^^^ Use `foo.minmax` instead of `[foo.min, foo.max]`.
12+
RUBY
13+
end
14+
15+
it 'does not register an offense if the receivers do not match' do
16+
expect_no_offenses(<<-RUBY.strip_indent)
17+
[foo.min, bar.max]
18+
RUBY
19+
end
20+
21+
it 'does not register an offense if there are additional elements' do
22+
expect_no_offenses(<<-RUBY.strip_indent)
23+
[foo.min, foo.baz, foo.max]
24+
RUBY
25+
end
26+
27+
it 'auto-corrects an offense to use `#minmax`' do
28+
corrected = autocorrect_source(<<-RUBY.strip_indent)
29+
[foo.bar.min, foo.bar.max]
30+
RUBY
31+
32+
expect(corrected).to eq(<<-RUBY.strip_indent)
33+
foo.bar.minmax
34+
RUBY
35+
end
36+
end
37+
38+
context 'when the expression is used in a parallel assignment' do
39+
it 'registers an offense if the receivers match' do
40+
expect_offense(<<-RUBY.strip_indent)
41+
bar = foo.min, foo.max
42+
^^^^^^^^^^^^^^^^ Use `foo.minmax` instead of `foo.min, foo.max`.
43+
RUBY
44+
end
45+
46+
it 'does not register an offense if the receivers do not match' do
47+
expect_no_offenses(<<-RUBY.strip_indent)
48+
baz = foo.min, bar.max
49+
RUBY
50+
end
51+
52+
it 'does not register an offense if there are additional elements' do
53+
expect_no_offenses(<<-RUBY.strip_indent)
54+
bar = foo.min, foo.baz, foo.max
55+
RUBY
56+
end
57+
58+
it 'auto-corrects an offense to use `#minmax`' do
59+
corrected = autocorrect_source(<<-RUBY.strip_indent)
60+
baz = foo.bar.min, foo.bar.max
61+
RUBY
62+
63+
expect(corrected).to eq(<<-RUBY.strip_indent)
64+
baz = foo.bar.minmax
65+
RUBY
66+
end
67+
end
68+
69+
context 'when the expression is used as a return value' do
70+
it 'registers an offense if the receivers match' do
71+
expect_offense(<<-RUBY.strip_indent)
72+
return foo.min, foo.max
73+
^^^^^^^^^^^^^^^^ Use `foo.minmax` instead of `foo.min, foo.max`.
74+
RUBY
75+
end
76+
77+
it 'does not register an offense if the receivers do not match' do
78+
expect_no_offenses(<<-RUBY.strip_indent)
79+
return foo.min, bar.max
80+
RUBY
81+
end
82+
83+
it 'does not register an offense if there are additional elements' do
84+
expect_no_offenses(<<-RUBY.strip_indent)
85+
return foo.min, foo.baz, foo.max
86+
RUBY
87+
end
88+
89+
it 'auto-corrects an offense to use `#minmax`' do
90+
corrected = autocorrect_source(<<-RUBY.strip_indent)
91+
return foo.bar.min, foo.bar.max
92+
RUBY
93+
94+
expect(corrected).to eq(<<-RUBY.strip_indent)
95+
return foo.bar.minmax
96+
RUBY
97+
end
98+
end
99+
end
100+
end

0 commit comments

Comments
 (0)