rubocop-performance 1.6.1 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
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.