rubocop-performance 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 111d420207542c8522d9f34b5b8bf24356572b5fbcf0c11dfcb1e7f738a4c76f
4
- data.tar.gz: 97632031f51e154320bc3d585da448740596436882b67277f1b5d26ddfe55fba
3
+ metadata.gz: 5be372d62d20424e05201d4b78bb06bf19fc707d845d6b799599ba72958f72b7
4
+ data.tar.gz: 87c59d78e37de238add70195fa26c95d31b23eb3065695f443a1694d3acf9145
5
5
  SHA512:
6
- metadata.gz: a36e6950e6fe7847303c87862df76767d32c4f68640c229ad4ceb6115d4ae2093be18a8296c2e49324c67e8eb06dd99db42f8823586f1b572e10a74693b831d1
7
- data.tar.gz: 782352b1a7c420ac3c5498aeff749577e6280fb35d6e36844ae875511e9b6b44becfd9a2789d1b4c672cb71a91b8ab1cc63c89ec41abda8d1f34f1dacd26a07c
6
+ metadata.gz: d6caf119ca1a8b11d829ed555f3a0653a1087d3a3ee3be078460c9ecfb5a5fb236a7abf2c058f57647d4f74dffccc66aebcf4d330f5a6a5bd5deaa12a8fea480
7
+ data.tar.gz: d8a6e634d4453300d24e5869bffa690457d07048b1e2978cc00dbc930c40e2dc80789a6faa95823e63414e5c579f8784cbe4f743607e2fd9598cf478aa1801ed
@@ -1,5 +1,16 @@
1
1
  # This is the default configuration file.
2
2
 
3
+ Performance/AncestorsInclude:
4
+ Description: 'Use `A <= B` instead of `A.ancestors.include?(B)`.'
5
+ Reference: 'https://github.com/JuanitoFatas/fast-ruby#ancestorsinclude-vs--code'
6
+ Enabled: 'pending'
7
+ VersionAdded: '1.7'
8
+
9
+ Performance/BigDecimalWithNumericArgument:
10
+ Description: 'Convert numeric argument to string before passing to BigDecimal.'
11
+ Enabled: 'pending'
12
+ VersionAdded: '1.7'
13
+
3
14
  Performance/BindCall:
4
15
  Description: 'Use `bind_call(obj, args, ...)` instead of `bind(obj).call(args, ...)`.'
5
16
  Enabled: true
@@ -131,6 +142,12 @@ Performance/InefficientHashSearch:
131
142
  VersionAdded: '0.56'
132
143
  Safe: false
133
144
 
145
+ Performance/IoReadlines:
146
+ Description: 'Use `IO.each_line` (`IO#each_line`) instead of `IO.readlines` (`IO#readlines`).'
147
+ Reference: 'https://docs.gitlab.com/ee/development/performance.html#reading-from-files-and-other-data-sources'
148
+ Enabled: false
149
+ VersionAdded: '1.7'
150
+
134
151
  Performance/OpenStruct:
135
152
  Description: 'Use `Struct` instead of `OpenStruct`.'
136
153
  Enabled: false
@@ -138,10 +155,11 @@ Performance/OpenStruct:
138
155
  Safe: false
139
156
 
140
157
  Performance/RangeInclude:
141
- Description: 'Use `Range#cover?` instead of `Range#include?`.'
158
+ Description: 'Use `Range#cover?` instead of `Range#include?` (or `Range#member?`).'
142
159
  Reference: 'https://github.com/JuanitoFatas/fast-ruby#cover-vs-include-code'
143
160
  Enabled: true
144
161
  VersionAdded: '0.36'
162
+ VersionChanged: '1.7'
145
163
  Safe: false
146
164
 
147
165
  Performance/RedundantBlockCall:
@@ -165,6 +183,16 @@ Performance/RedundantMerge:
165
183
  # Max number of key-value pairs to consider an offense
166
184
  MaxKeyValuePairs: 2
167
185
 
186
+ Performance/RedundantSortBlock:
187
+ Description: 'Use `sort` instead of `sort { |a, b| a <=> b }`.'
188
+ Enabled: 'pending'
189
+ VersionAdded: '1.7'
190
+
191
+ Performance/RedundantStringChars:
192
+ Description: 'Checks for redundant `String#chars`.'
193
+ Enabled: 'pending'
194
+ VersionAdded: '1.7'
195
+
168
196
  Performance/RegexpMatch:
169
197
  Description: >-
170
198
  Use `match?` instead of `Regexp#match`, `String#match`, `Symbol#match`,
@@ -179,6 +207,11 @@ Performance/ReverseEach:
179
207
  Enabled: true
180
208
  VersionAdded: '0.30'
181
209
 
210
+ Performance/ReverseFirst:
211
+ Description: 'Use `last(n).reverse` instead of `reverse.first(n)`.'
212
+ Enabled: 'pending'
213
+ VersionAdded: '1.7'
214
+
182
215
  Performance/Size:
183
216
  Description: >-
184
217
  Use `size` instead of `count` for counting
@@ -187,6 +220,17 @@ Performance/Size:
187
220
  Enabled: true
188
221
  VersionAdded: '0.30'
189
222
 
223
+ Performance/SortReverse:
224
+ Description: 'Use `sort.reverse` instead of `sort { |a, b| b <=> a }`.'
225
+ Enabled: 'pending'
226
+ VersionAdded: '1.7'
227
+
228
+ Performance/Squeeze:
229
+ Description: "Use `squeeze('a')` instead of `gsub(/a+/, 'a')`."
230
+ Reference: 'https://github.com/JuanitoFatas/fast-ruby#remove-extra-spaces-or-other-contiguous-characters-code'
231
+ Enabled: 'pending'
232
+ VersionAdded: '1.7'
233
+
190
234
  Performance/StartWith:
191
235
  Description: 'Use `start_with?` instead of a regex match anchored to the beginning of a string.'
192
236
  Reference: 'https://github.com/JuanitoFatas/fast-ruby#stringmatch-vs-stringstart_withstringend_with-code-start-code-end'
@@ -200,6 +244,11 @@ Performance/StartWith:
200
244
  VersionAdded: '0.36'
201
245
  VersionChanged: '1.6'
202
246
 
247
+ Performance/StringInclude:
248
+ Description: 'Use `String#include?` instead of a regex match with literal-only pattern.'
249
+ Enabled: 'pending'
250
+ VersionAdded: '1.7'
251
+
203
252
  Performance/StringReplacement:
204
253
  Description: >-
205
254
  Use `tr` instead of `gsub` when you are replacing the same
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ # Common functionality for cops checking `Enumerable#sort` blocks.
6
+ module SortBlock
7
+ extend NodePattern::Macros
8
+ include RangeHelp
9
+
10
+ def_node_matcher :sort_with_block?, <<~PATTERN
11
+ (block
12
+ $(send _ :sort)
13
+ (args (arg $_a) (arg $_b))
14
+ $send)
15
+ PATTERN
16
+
17
+ def_node_matcher :replaceable_body?, <<~PATTERN
18
+ (send (lvar %1) :<=> (lvar %2))
19
+ PATTERN
20
+
21
+ private
22
+
23
+ def sort_range(send, node)
24
+ range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop is used to identify usages of `ancestors.include?` and
7
+ # change them to use `<=` instead.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # A.ancestors.include?(B)
12
+ #
13
+ # # good
14
+ # A <= B
15
+ #
16
+ class AncestorsInclude < Cop
17
+ include RangeHelp
18
+
19
+ MSG = 'Use `<=` instead of `ancestors.include?`.'
20
+
21
+ def_node_matcher :ancestors_include_candidate?, <<~PATTERN
22
+ (send (send $_subclass :ancestors) :include? $_superclass)
23
+ PATTERN
24
+
25
+ def on_send(node)
26
+ return unless ancestors_include_candidate?(node)
27
+
28
+ location_of_ancestors = node.children[0].loc.selector.begin_pos
29
+ end_location = node.loc.selector.end_pos
30
+ range = range_between(location_of_ancestors, end_location)
31
+
32
+ add_offense(node, location: range)
33
+ end
34
+
35
+ def autocorrect(node)
36
+ ancestors_include_candidate?(node) do |subclass, superclass|
37
+ lambda do |corrector|
38
+ corrector.replace(node, "#{subclass.source} <= #{superclass.source}")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where numeric argument to BigDecimal should be
7
+ # converted to string. Initializing from String is faster
8
+ # than from Numeric for BigDecimal.
9
+ #
10
+ # @example
11
+ #
12
+ # # bad
13
+ # BigDecimal(1, 2)
14
+ # BigDecimal(1.2, 3, exception: true)
15
+ #
16
+ # # good
17
+ # BigDecimal('1', 2)
18
+ # BigDecimal('1.2', 3, exception: true)
19
+ #
20
+ class BigDecimalWithNumericArgument < Cop
21
+ MSG = 'Convert numeric argument to string before passing to `BigDecimal`.'
22
+
23
+ def_node_matcher :big_decimal_with_numeric_argument?, <<~PATTERN
24
+ (send nil? :BigDecimal $numeric_type? ...)
25
+ PATTERN
26
+
27
+ def on_send(node)
28
+ big_decimal_with_numeric_argument?(node) do |numeric|
29
+ add_offense(node, location: numeric.source_range)
30
+ end
31
+ end
32
+
33
+ def autocorrect(node)
34
+ big_decimal_with_numeric_argument?(node) do |numeric|
35
+ lambda do |corrector|
36
+ corrector.wrap(numeric, "'", "'")
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where inefficient `readlines` method
7
+ # can be replaced by `each_line` to avoid fully loading file content into memory.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # File.readlines('testfile').each { |l| puts l }
13
+ # IO.readlines('testfile', chomp: true).each { |l| puts l }
14
+ #
15
+ # conn.readlines(10).map { |l| l.size }
16
+ # file.readlines.find { |l| l.start_with?('#') }
17
+ # file.readlines.each { |l| puts l }
18
+ #
19
+ # # good
20
+ # File.open('testfile', 'r').each_line { |l| puts l }
21
+ # IO.open('testfile').each_line(chomp: true) { |l| puts l }
22
+ #
23
+ # conn.each_line(10).map { |l| l.size }
24
+ # file.each_line.find { |l| l.start_with?('#') }
25
+ # file.each_line { |l| puts l }
26
+ #
27
+ class IoReadlines < Cop
28
+ include RangeHelp
29
+
30
+ MSG = 'Use `%<good>s` instead of `%<bad>s`.'
31
+ ENUMERABLE_METHODS = (Enumerable.instance_methods + [:each]).freeze
32
+
33
+ def_node_matcher :readlines_on_class?, <<~PATTERN
34
+ $(send $(send (const nil? {:IO :File}) :readlines ...) #enumerable_method?)
35
+ PATTERN
36
+
37
+ def_node_matcher :readlines_on_instance?, <<~PATTERN
38
+ $(send $(send ${nil? !const_type?} :readlines ...) #enumerable_method? ...)
39
+ PATTERN
40
+
41
+ def on_send(node)
42
+ readlines_on_class?(node) do |enumerable_call, readlines_call|
43
+ offense(node, enumerable_call, readlines_call)
44
+ end
45
+
46
+ readlines_on_instance?(node) do |enumerable_call, readlines_call, _|
47
+ offense(node, enumerable_call, readlines_call)
48
+ end
49
+ end
50
+
51
+ def autocorrect(node)
52
+ readlines_on_instance?(node) do |enumerable_call, readlines_call, receiver|
53
+ # We cannot safely correct `.readlines` method called on IO/File classes
54
+ # due to its signature and we are not sure with implicit receiver
55
+ # if it is called in the context of some instance or mentioned class.
56
+ return if receiver.nil?
57
+
58
+ lambda do |corrector|
59
+ range = correction_range(enumerable_call, readlines_call)
60
+
61
+ if readlines_call.arguments?
62
+ call_args = build_call_args(readlines_call.arguments)
63
+ replacement = "each_line(#{call_args})"
64
+ else
65
+ replacement = 'each_line'
66
+ end
67
+
68
+ corrector.replace(range, replacement)
69
+ end
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def enumerable_method?(node)
76
+ ENUMERABLE_METHODS.include?(node.to_sym)
77
+ end
78
+
79
+ def offense(node, enumerable_call, readlines_call)
80
+ range = offense_range(enumerable_call, readlines_call)
81
+ good_method = build_good_method(enumerable_call)
82
+ bad_method = build_bad_method(enumerable_call)
83
+
84
+ add_offense(
85
+ node,
86
+ location: range,
87
+ message: format(MSG, good: good_method, bad: bad_method)
88
+ )
89
+ end
90
+
91
+ def offense_range(enumerable_call, readlines_call)
92
+ readlines_pos = readlines_call.loc.selector.begin_pos
93
+ enumerable_pos = enumerable_call.loc.selector.end_pos
94
+ range_between(readlines_pos, enumerable_pos)
95
+ end
96
+
97
+ def build_good_method(enumerable_call)
98
+ if enumerable_call.method?(:each)
99
+ 'each_line'
100
+ else
101
+ "each_line.#{enumerable_call.method_name}"
102
+ end
103
+ end
104
+
105
+ def build_bad_method(enumerable_call)
106
+ "readlines.#{enumerable_call.method_name}"
107
+ end
108
+
109
+ def correction_range(enumerable_call, readlines_call)
110
+ begin_pos = readlines_call.loc.selector.begin_pos
111
+
112
+ end_pos = if enumerable_call.method?(:each)
113
+ enumerable_call.loc.expression.end_pos
114
+ else
115
+ enumerable_call.loc.dot.begin_pos
116
+ end
117
+
118
+ range_between(begin_pos, end_pos)
119
+ end
120
+
121
+ def build_call_args(call_args_node)
122
+ call_args_node.map(&:source).join(', ')
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -3,18 +3,19 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop identifies uses of `Range#include?`, which iterates over each
6
+ # This cop identifies uses of `Range#include?` and `Range#member?`, which iterates over each
7
7
  # item in a `Range` to see if a specified item is there. In contrast,
8
8
  # `Range#cover?` simply compares the target item with the beginning and
9
9
  # end points of the `Range`. In a great majority of cases, this is what
10
10
  # is wanted.
11
11
  #
12
- # This cop is `Safe: false` by default because `Range#include?` and
12
+ # This cop is `Safe: false` by default because `Range#include?` (or `Range#member?`) and
13
13
  # `Range#cover?` are not equivalent behaviour.
14
14
  #
15
15
  # @example
16
16
  # # bad
17
17
  # ('a'..'z').include?('b') # => true
18
+ # ('a'..'z').member?('b') # => true
18
19
  #
19
20
  # # good
20
21
  # ('a'..'z').cover?('b') # => true
@@ -24,7 +25,7 @@ module RuboCop
24
25
  #
25
26
  # ('a'..'z').cover?('yellow') # => true
26
27
  class RangeInclude < Cop
27
- MSG = 'Use `Range#cover?` instead of `Range#include?`.'
28
+ MSG = 'Use `Range#cover?` instead of `Range#%<bad_method>s`.'
28
29
 
29
30
  # TODO: If we traced out assignments of variables to their uses, we
30
31
  # might pick up on a few more instances of this issue
@@ -32,13 +33,14 @@ module RuboCop
32
33
  # (We don't even catch it if the Range is in double parens)
33
34
 
34
35
  def_node_matcher :range_include, <<~PATTERN
35
- (send {irange erange (begin {irange erange})} :include? ...)
36
+ (send {irange erange (begin {irange erange})} ${:include? :member?} ...)
36
37
  PATTERN
37
38
 
38
39
  def on_send(node)
39
- return unless range_include(node)
40
-
41
- add_offense(node, location: :selector)
40
+ range_include(node) do |bad_method|
41
+ message = format(MSG, bad_method: bad_method)
42
+ add_offense(node, location: :selector, message: message)
43
+ end
42
44
  end
43
45
 
44
46
  def autocorrect(node)
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `sort { |a, b| a <=> b }`
7
+ # can be replaced with `sort`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # array.sort { |a, b| a <=> b }
12
+ #
13
+ # # good
14
+ # array.sort
15
+ #
16
+ class RedundantSortBlock < Cop
17
+ include SortBlock
18
+
19
+ MSG = 'Use `sort` instead of `%<bad_method>s`.'
20
+
21
+ def on_block(node)
22
+ sort_with_block?(node) do |send, var_a, var_b, body|
23
+ replaceable_body?(body, var_a, var_b) do
24
+ range = sort_range(send, node)
25
+
26
+ add_offense(
27
+ node,
28
+ location: range,
29
+ message: message(var_a, var_b)
30
+ )
31
+ end
32
+ end
33
+ end
34
+
35
+ def autocorrect(node)
36
+ sort_with_block?(node) do |send, _var_a, _var_b, _body|
37
+ lambda do |corrector|
38
+ range = sort_range(send, node)
39
+ corrector.replace(range, 'sort')
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def message(var_a, var_b)
47
+ bad_method = "sort { |#{var_a}, #{var_b}| #{var_a} <=> #{var_b} }"
48
+ format(MSG, bad_method: bad_method)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop checks for redundant `String#chars`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # str.chars[0..2]
11
+ # str.chars.slice(0..2)
12
+ #
13
+ # # good
14
+ # str[0..2].chars
15
+ #
16
+ # # bad
17
+ # str.chars.first
18
+ # str.chars.first(2)
19
+ # str.chars.last
20
+ # str.chars.last(2)
21
+ #
22
+ # # good
23
+ # str[0]
24
+ # str[0...2].chars
25
+ # str[-1]
26
+ # str[-2..-1].chars
27
+ #
28
+ # # bad
29
+ # str.chars.take(2)
30
+ # str.chars.drop(2)
31
+ # str.chars.length
32
+ # str.chars.size
33
+ # str.chars.empty?
34
+ #
35
+ # # good
36
+ # str[0...2].chars
37
+ # str[2..-1].chars
38
+ # str.length
39
+ # str.size
40
+ # str.empty?
41
+ #
42
+ class RedundantStringChars < Cop
43
+ include RangeHelp
44
+
45
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
46
+ REPLACEABLE_METHODS = %i[[] slice first last take drop length size empty?].freeze
47
+
48
+ def_node_matcher :redundant_chars_call?, <<~PATTERN
49
+ (send $(send _ :chars) $#replaceable_method? $...)
50
+ PATTERN
51
+
52
+ def on_send(node)
53
+ redundant_chars_call?(node) do |receiver, method, args|
54
+ range = offense_range(receiver, node)
55
+ message = build_message(method, args)
56
+ add_offense(node, location: range, message: message)
57
+ end
58
+ end
59
+
60
+ def autocorrect(node)
61
+ redundant_chars_call?(node) do |receiver, method, args|
62
+ range = correction_range(receiver, node)
63
+ replacement = build_good_method(method, args)
64
+
65
+ lambda do |corrector|
66
+ corrector.replace(range, replacement)
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def replaceable_method?(method_name)
74
+ REPLACEABLE_METHODS.include?(method_name)
75
+ end
76
+
77
+ def offense_range(receiver, node)
78
+ range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
79
+ end
80
+
81
+ def correction_range(receiver, node)
82
+ range_between(receiver.loc.dot.begin_pos, node.loc.expression.end_pos)
83
+ end
84
+
85
+ def build_message(method, args)
86
+ good_method = build_good_method(method, args)
87
+ bad_method = build_bad_method(method, args)
88
+ format(MSG, good_method: good_method, bad_method: bad_method)
89
+ end
90
+
91
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
92
+ def build_good_method(method, args)
93
+ case method
94
+ when :[], :slice
95
+ "[#{build_call_args(args)}].chars"
96
+ when :first
97
+ if args.any?
98
+ "[0...#{args.first.source}].chars"
99
+ else
100
+ '[0]'
101
+ end
102
+ when :last
103
+ if args.any?
104
+ "[-#{args.first.source}..-1].chars"
105
+ else
106
+ '[-1]'
107
+ end
108
+ when :take
109
+ "[0...#{args.first.source}].chars"
110
+ when :drop
111
+ "[#{args.first.source}..-1].chars"
112
+ else
113
+ ".#{method}"
114
+ end
115
+ end
116
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
117
+
118
+ def build_bad_method(method, args)
119
+ case method
120
+ when :[]
121
+ "chars[#{build_call_args(args)}]"
122
+ else
123
+ if args.any?
124
+ "chars.#{method}(#{build_call_args(args)})"
125
+ else
126
+ "chars.#{method}"
127
+ end
128
+ end
129
+ end
130
+
131
+ def build_call_args(call_args_node)
132
+ call_args_node.map(&:source).join(', ')
133
+ end
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `reverse.first(n)` and `reverse.first`
7
+ # can be replaced by `last(n).reverse` and `last`.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # array.reverse.first(5)
13
+ # array.reverse.first
14
+ #
15
+ # # good
16
+ # array.last(5).reverse
17
+ # array.last
18
+ #
19
+ class ReverseFirst < Cop
20
+ include RangeHelp
21
+
22
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
23
+
24
+ def_node_matcher :reverse_first_candidate?, <<~PATTERN
25
+ (send $(send _ :reverse) :first (int _)?)
26
+ PATTERN
27
+
28
+ def on_send(node)
29
+ reverse_first_candidate?(node) do |receiver|
30
+ range = correction_range(receiver, node)
31
+ message = build_message(node)
32
+
33
+ add_offense(node, location: range, message: message)
34
+ end
35
+ end
36
+
37
+ def autocorrect(node)
38
+ reverse_first_candidate?(node) do |receiver|
39
+ range = correction_range(receiver, node)
40
+ replacement = build_good_method(node)
41
+
42
+ lambda do |corrector|
43
+ corrector.replace(range, replacement)
44
+ end
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ def correction_range(receiver, node)
51
+ range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
52
+ end
53
+
54
+ def build_message(node)
55
+ good_method = build_good_method(node)
56
+ bad_method = build_bad_method(node)
57
+ format(MSG, good_method: good_method, bad_method: bad_method)
58
+ end
59
+
60
+ def build_good_method(node)
61
+ if node.arguments?
62
+ "last(#{node.arguments.first.source}).reverse"
63
+ else
64
+ 'last'
65
+ end
66
+ end
67
+
68
+ def build_bad_method(node)
69
+ if node.arguments?
70
+ "reverse.first(#{node.arguments.first.source})"
71
+ else
72
+ 'reverse.first'
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -9,15 +9,27 @@ module RuboCop
9
9
  # @example
10
10
  # # bad
11
11
  # [1, 2, 3].count
12
+ # (1..3).to_a.count
13
+ # Array[*1..3].count
14
+ # Array(1..3).count
12
15
  #
13
16
  # # bad
14
17
  # {a: 1, b: 2, c: 3}.count
18
+ # [[:foo, :bar], [1, 2]].to_h.count
19
+ # Hash[*('a'..'z')].count
20
+ # Hash(key: :value).count
15
21
  #
16
22
  # # good
17
23
  # [1, 2, 3].size
24
+ # (1..3).to_a.size
25
+ # Array[*1..3].size
26
+ # Array(1..3).size
18
27
  #
19
28
  # # good
20
29
  # {a: 1, b: 2, c: 3}.size
30
+ # [[:foo, :bar], [1, 2]].to_h.size
31
+ # Hash[*('a'..'z')].size
32
+ # Hash(key: :value).size
21
33
  #
22
34
  # # good
23
35
  # [1, 2, 3].count { |e| e > 2 }
@@ -26,8 +38,30 @@ module RuboCop
26
38
  class Size < Cop
27
39
  MSG = 'Use `size` instead of `count`.'
28
40
 
41
+ def_node_matcher :array?, <<~PATTERN
42
+ {
43
+ [!nil? array_type?]
44
+ (send _ :to_a)
45
+ (send (const nil? :Array) :[] _)
46
+ (send nil? :Array _)
47
+ }
48
+ PATTERN
49
+
50
+ def_node_matcher :hash?, <<~PATTERN
51
+ {
52
+ [!nil? hash_type?]
53
+ (send _ :to_h)
54
+ (send (const nil? :Hash) :[] _)
55
+ (send nil? :Hash _)
56
+ }
57
+ PATTERN
58
+
59
+ def_node_matcher :count?, <<~PATTERN
60
+ (send {#array? #hash?} :count)
61
+ PATTERN
62
+
29
63
  def on_send(node)
30
- return unless eligible_node?(node)
64
+ return if node.parent&.block_type? || !count?(node)
31
65
 
32
66
  add_offense(node, location: :selector)
33
67
  end
@@ -35,42 +69,6 @@ module RuboCop
35
69
  def autocorrect(node)
36
70
  ->(corrector) { corrector.replace(node.loc.selector, 'size') }
37
71
  end
38
-
39
- private
40
-
41
- def eligible_node?(node)
42
- return false unless node.method?(:count) && !node.arguments?
43
-
44
- eligible_receiver?(node.receiver) && !allowed_parent?(node.parent)
45
- end
46
-
47
- def eligible_receiver?(node)
48
- return false unless node
49
-
50
- array?(node) || hash?(node)
51
- end
52
-
53
- def allowed_parent?(node)
54
- node&.block_type?
55
- end
56
-
57
- def array?(node)
58
- return true if node.array_type?
59
- return false unless node.send_type?
60
-
61
- _, constant = *node.receiver
62
-
63
- constant == :Array || node.method?(:to_a)
64
- end
65
-
66
- def hash?(node)
67
- return true if node.hash_type?
68
- return false unless node.send_type?
69
-
70
- _, constant = *node.receiver
71
-
72
- constant == :Hash || node.method?(:to_h)
73
- end
74
72
  end
75
73
  end
76
74
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `sort { |a, b| b <=> a }`
7
+ # can be replaced by a faster `sort.reverse`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # array.sort { |a, b| b <=> a }
12
+ #
13
+ # # good
14
+ # array.sort.reverse
15
+ #
16
+ class SortReverse < Cop
17
+ include SortBlock
18
+
19
+ MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
20
+
21
+ def on_block(node)
22
+ sort_with_block?(node) do |send, var_a, var_b, body|
23
+ replaceable_body?(body, var_b, var_a) do
24
+ range = sort_range(send, node)
25
+
26
+ add_offense(
27
+ node,
28
+ location: range,
29
+ message: message(var_a, var_b)
30
+ )
31
+ end
32
+ end
33
+ end
34
+
35
+ def autocorrect(node)
36
+ sort_with_block?(node) do |send, _var_a, _var_b, _body|
37
+ lambda do |corrector|
38
+ range = sort_range(send, node)
39
+ replacement = 'sort.reverse'
40
+ corrector.replace(range, replacement)
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def message(var_a, var_b)
48
+ bad_method = "sort { |#{var_a}, #{var_b}| #{var_b} <=> #{var_a} }"
49
+ format(MSG, bad_method: bad_method)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `gsub(/a+/, 'a')` and `gsub!(/a+/, 'a')`
7
+ # can be replaced by `squeeze('a')` and `squeeze!('a')`.
8
+ #
9
+ # The `squeeze('a')` method is faster than `gsub(/a+/, 'a')`.
10
+ #
11
+ # @example
12
+ #
13
+ # # bad
14
+ # str.gsub(/a+/, 'a')
15
+ # str.gsub!(/a+/, 'a')
16
+ #
17
+ # # good
18
+ # str.squeeze('a')
19
+ # str.squeeze!('a')
20
+ #
21
+ class Squeeze < Cop
22
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
23
+
24
+ PREFERRED_METHODS = {
25
+ gsub: :squeeze,
26
+ gsub!: :squeeze!
27
+ }.freeze
28
+
29
+ def_node_matcher :squeeze_candidate?, <<~PATTERN
30
+ (send
31
+ $!nil? ${:gsub :gsub!}
32
+ (regexp
33
+ (str $#repeating_literal?)
34
+ (regopt))
35
+ (str $_))
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ squeeze_candidate?(node) do |_, bad_method, regexp_str, replace_str|
40
+ regexp_str = regexp_str[0..-2] # delete '+' from the end
41
+ regexp_str = interpret_string_escapes(regexp_str)
42
+ return unless replace_str == regexp_str
43
+
44
+ good_method = PREFERRED_METHODS[bad_method]
45
+ message = format(MSG, current: bad_method, prefer: good_method)
46
+ add_offense(node, location: :selector, message: message)
47
+ end
48
+ end
49
+
50
+ def autocorrect(node)
51
+ squeeze_candidate?(node) do |receiver, bad_method, _regexp_str, replace_str|
52
+ lambda do |corrector|
53
+ good_method = PREFERRED_METHODS[bad_method]
54
+ string_literal = to_string_literal(replace_str)
55
+
56
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
57
+ corrector.replace(node.source_range, new_code)
58
+ end
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def repeating_literal?(regex_str)
65
+ regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies unnecessary use of a regex where
7
+ # `String#include?` would suffice.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # 'abc'.match?(/ab/)
12
+ # /ab/.match?('abc')
13
+ # 'abc' =~ /ab/
14
+ # /ab/ =~ 'abc'
15
+ # 'abc'.match(/ab/)
16
+ # /ab/.match('abc')
17
+ #
18
+ # # good
19
+ # 'abc'.include?('ab')
20
+ class StringInclude < Cop
21
+ MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
22
+
23
+ def_node_matcher :redundant_regex?, <<~PATTERN
24
+ {(send $!nil? {:match :=~ :match?} (regexp (str $#literal?) (regopt)))
25
+ (send (regexp (str $#literal?) (regopt)) {:match :match?} $str)
26
+ (match-with-lvasgn (regexp (str $#literal?) (regopt)) $_)}
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ return unless redundant_regex?(node)
31
+
32
+ add_offense(node)
33
+ end
34
+ alias on_match_with_lvasgn on_send
35
+
36
+ def autocorrect(node)
37
+ redundant_regex?(node) do |receiver, regex_str|
38
+ receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
39
+ regex_str = interpret_string_escapes(regex_str)
40
+
41
+ lambda do |corrector|
42
+ new_source = receiver.source + '.include?(' +
43
+ to_string_literal(regex_str) + ')'
44
+ corrector.replace(node.source_range, new_source)
45
+ end
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def literal?(regex_str)
52
+ regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/)
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,7 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'mixin/regexp_metacharacter'
4
+ require_relative 'mixin/sort_block'
4
5
 
6
+ require_relative 'performance/ancestors_include'
7
+ require_relative 'performance/big_decimal_with_numeric_argument'
5
8
  require_relative 'performance/bind_call'
6
9
  require_relative 'performance/caller'
7
10
  require_relative 'performance/case_when_splat'
@@ -18,13 +21,20 @@ require_relative 'performance/flat_map'
18
21
  require_relative 'performance/inefficient_hash_search'
19
22
  require_relative 'performance/open_struct'
20
23
  require_relative 'performance/range_include'
24
+ require_relative 'performance/io_readlines'
21
25
  require_relative 'performance/redundant_block_call'
22
26
  require_relative 'performance/redundant_match'
23
27
  require_relative 'performance/redundant_merge'
28
+ require_relative 'performance/redundant_sort_block'
29
+ require_relative 'performance/redundant_string_chars'
24
30
  require_relative 'performance/regexp_match'
25
31
  require_relative 'performance/reverse_each'
32
+ require_relative 'performance/reverse_first'
26
33
  require_relative 'performance/size'
34
+ require_relative 'performance/sort_reverse'
35
+ require_relative 'performance/squeeze'
27
36
  require_relative 'performance/start_with'
37
+ require_relative 'performance/string_include'
28
38
  require_relative 'performance/string_replacement'
29
39
  require_relative 'performance/times_map'
30
40
  require_relative 'performance/unfreeze_string'
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Performance
5
5
  module Version
6
- STRING = '1.6.1'
6
+ STRING = '1.7.0'
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-performance
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.1
4
+ version: 1.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bozhidar Batsov
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-06-05 00:00:00.000000000 Z
13
+ date: 2020-07-07 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rubocop
@@ -18,14 +18,14 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: 0.71.0
21
+ version: 0.82.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
- version: 0.71.0
28
+ version: 0.82.0
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: simplecov
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -55,6 +55,9 @@ files:
55
55
  - config/default.yml
56
56
  - lib/rubocop-performance.rb
57
57
  - lib/rubocop/cop/mixin/regexp_metacharacter.rb
58
+ - lib/rubocop/cop/mixin/sort_block.rb
59
+ - lib/rubocop/cop/performance/ancestors_include.rb
60
+ - lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb
58
61
  - lib/rubocop/cop/performance/bind_call.rb
59
62
  - lib/rubocop/cop/performance/caller.rb
60
63
  - lib/rubocop/cop/performance/case_when_splat.rb
@@ -70,15 +73,22 @@ files:
70
73
  - lib/rubocop/cop/performance/fixed_size.rb
71
74
  - lib/rubocop/cop/performance/flat_map.rb
72
75
  - lib/rubocop/cop/performance/inefficient_hash_search.rb
76
+ - lib/rubocop/cop/performance/io_readlines.rb
73
77
  - lib/rubocop/cop/performance/open_struct.rb
74
78
  - lib/rubocop/cop/performance/range_include.rb
75
79
  - lib/rubocop/cop/performance/redundant_block_call.rb
76
80
  - lib/rubocop/cop/performance/redundant_match.rb
77
81
  - lib/rubocop/cop/performance/redundant_merge.rb
82
+ - lib/rubocop/cop/performance/redundant_sort_block.rb
83
+ - lib/rubocop/cop/performance/redundant_string_chars.rb
78
84
  - lib/rubocop/cop/performance/regexp_match.rb
79
85
  - lib/rubocop/cop/performance/reverse_each.rb
86
+ - lib/rubocop/cop/performance/reverse_first.rb
80
87
  - lib/rubocop/cop/performance/size.rb
88
+ - lib/rubocop/cop/performance/sort_reverse.rb
89
+ - lib/rubocop/cop/performance/squeeze.rb
81
90
  - lib/rubocop/cop/performance/start_with.rb
91
+ - lib/rubocop/cop/performance/string_include.rb
82
92
  - lib/rubocop/cop/performance/string_replacement.rb
83
93
  - lib/rubocop/cop/performance/times_map.rb
84
94
  - lib/rubocop/cop/performance/unfreeze_string.rb
@@ -111,7 +121,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
121
  - !ruby/object:Gem::Version
112
122
  version: '0'
113
123
  requirements: []
114
- rubygems_version: 3.1.3
124
+ rubygems_version: 3.1.4
115
125
  signing_key:
116
126
  specification_version: 4
117
127
  summary: Automatic performance checking tool for Ruby code.