rubocop-performance 1.6.0 → 1.8.1

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.
Files changed (47) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +1 -1
  3. data/config/default.yml +77 -10
  4. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +39 -4
  5. data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
  6. data/lib/rubocop/cop/performance/ancestors_include.rb +48 -0
  7. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +45 -0
  8. data/lib/rubocop/cop/performance/bind_call.rb +8 -18
  9. data/lib/rubocop/cop/performance/caller.rb +3 -2
  10. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  11. data/lib/rubocop/cop/performance/casecmp.rb +12 -20
  12. data/lib/rubocop/cop/performance/chain_array_allocation.rb +4 -10
  13. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  14. data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
  15. data/lib/rubocop/cop/performance/count.rb +13 -16
  16. data/lib/rubocop/cop/performance/delete_prefix.rb +43 -28
  17. data/lib/rubocop/cop/performance/delete_suffix.rb +43 -28
  18. data/lib/rubocop/cop/performance/detect.rb +63 -31
  19. data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
  20. data/lib/rubocop/cop/performance/end_with.rb +29 -17
  21. data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
  22. data/lib/rubocop/cop/performance/flat_map.rb +20 -22
  23. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +13 -14
  24. data/lib/rubocop/cop/performance/io_readlines.rb +116 -0
  25. data/lib/rubocop/cop/performance/open_struct.rb +2 -2
  26. data/lib/rubocop/cop/performance/range_include.rb +14 -11
  27. data/lib/rubocop/cop/performance/redundant_block_call.rb +11 -6
  28. data/lib/rubocop/cop/performance/redundant_match.rb +11 -6
  29. data/lib/rubocop/cop/performance/redundant_merge.rb +18 -17
  30. data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
  31. data/lib/rubocop/cop/performance/redundant_string_chars.rb +133 -0
  32. data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
  33. data/lib/rubocop/cop/performance/reverse_each.rb +9 -5
  34. data/lib/rubocop/cop/performance/reverse_first.rb +72 -0
  35. data/lib/rubocop/cop/performance/size.rb +41 -43
  36. data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
  37. data/lib/rubocop/cop/performance/squeeze.rb +66 -0
  38. data/lib/rubocop/cop/performance/start_with.rb +29 -17
  39. data/lib/rubocop/cop/performance/string_include.rb +55 -0
  40. data/lib/rubocop/cop/performance/string_replacement.rb +23 -27
  41. data/lib/rubocop/cop/performance/sum.rb +134 -0
  42. data/lib/rubocop/cop/performance/times_map.rb +11 -18
  43. data/lib/rubocop/cop/performance/unfreeze_string.rb +2 -2
  44. data/lib/rubocop/cop/performance/uri_default_parser.rb +6 -12
  45. data/lib/rubocop/cop/performance_cops.rb +12 -0
  46. data/lib/rubocop/performance/version.rb +1 -1
  47. metadata +33 -8
@@ -72,7 +72,9 @@ module RuboCop
72
72
  # do_something($~)
73
73
  # end
74
74
  # end
75
- class RegexpMatch < Cop
75
+ class RegexpMatch < Base
76
+ extend AutoCorrector
77
+
76
78
  # Constants are included in this list because it is unlikely that
77
79
  # someone will store `nil` as a constant and then use it for comparison
78
80
  TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
@@ -141,27 +143,28 @@ module RuboCop
141
143
  end
142
144
  end
143
145
 
144
- def autocorrect(node)
145
- lambda do |corrector|
146
- if match_method?(node) || match_with_int_arg_method?(node)
147
- corrector.replace(node.loc.selector, 'match?')
148
- elsif match_operator?(node) || match_threequals?(node)
149
- recv, oper, arg = *node
150
- correct_operator(corrector, recv, arg, oper)
151
- elsif match_with_lvasgn?(node)
152
- recv, arg = *node
153
- correct_operator(corrector, recv, arg)
154
- end
155
- end
156
- end
157
-
158
146
  private
159
147
 
160
148
  def check_condition(cond)
161
149
  match_node?(cond) do
162
150
  return if last_match_used?(cond)
163
151
 
164
- add_offense(cond)
152
+ message = message(cond)
153
+ add_offense(cond, message: message) do |corrector|
154
+ autocorrect(corrector, cond)
155
+ end
156
+ end
157
+ end
158
+
159
+ def autocorrect(corrector, node)
160
+ if match_method?(node) || match_with_int_arg_method?(node)
161
+ corrector.replace(node.loc.selector, 'match?')
162
+ elsif match_operator?(node) || match_threequals?(node)
163
+ recv, oper, arg = *node
164
+ correct_operator(corrector, recv, arg, oper)
165
+ elsif match_with_lvasgn?(node)
166
+ recv, arg = *node
167
+ correct_operator(corrector, recv, arg)
165
168
  end
166
169
  end
167
170
 
@@ -231,10 +234,7 @@ module RuboCop
231
234
 
232
235
  def scope_root(node)
233
236
  node.each_ancestor.find do |ancestor|
234
- ancestor.def_type? ||
235
- ancestor.defs_type? ||
236
- ancestor.class_type? ||
237
- ancestor.module_type?
237
+ ancestor.def_type? || ancestor.defs_type? || ancestor.class_type? || ancestor.module_type?
238
238
  end
239
239
  end
240
240
 
@@ -12,8 +12,9 @@ module RuboCop
12
12
  #
13
13
  # # good
14
14
  # [].reverse_each
15
- class ReverseEach < Cop
15
+ class ReverseEach < Base
16
16
  include RangeHelp
17
+ extend AutoCorrector
17
18
 
18
19
  MSG = 'Use `reverse_each` instead of `reverse.each`.'
19
20
  UNDERSCORE = '_'
@@ -29,13 +30,16 @@ module RuboCop
29
30
 
30
31
  range = range_between(location_of_reverse, end_location)
31
32
 
32
- add_offense(node, location: range)
33
+ add_offense(range) do |corrector|
34
+ corrector.replace(replacement_range(node), UNDERSCORE)
35
+ end
33
36
  end
34
37
  end
35
38
 
36
- def autocorrect(node)
37
- range = range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
38
- ->(corrector) { corrector.replace(range, UNDERSCORE) }
39
+ private
40
+
41
+ def replacement_range(node)
42
+ range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
39
43
  end
40
44
  end
41
45
  end
@@ -0,0 +1,72 @@
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 < Base
20
+ include RangeHelp
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
24
+
25
+ def_node_matcher :reverse_first_candidate?, <<~PATTERN
26
+ (send $(send _ :reverse) :first (int _)?)
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ reverse_first_candidate?(node) do |receiver|
31
+ range = correction_range(receiver, node)
32
+ message = build_message(node)
33
+
34
+ add_offense(range, message: message) do |corrector|
35
+ replacement = build_good_method(node)
36
+
37
+ corrector.replace(range, replacement)
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def correction_range(receiver, node)
45
+ range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
46
+ end
47
+
48
+ def build_message(node)
49
+ good_method = build_good_method(node)
50
+ bad_method = build_bad_method(node)
51
+ format(MSG, good_method: good_method, bad_method: bad_method)
52
+ end
53
+
54
+ def build_good_method(node)
55
+ if node.arguments?
56
+ "last(#{node.arguments.first.source}).reverse"
57
+ else
58
+ 'last'
59
+ end
60
+ end
61
+
62
+ def build_bad_method(node)
63
+ if node.arguments?
64
+ "reverse.first(#{node.arguments.first.source})"
65
+ else
66
+ 'reverse.first'
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -9,67 +9,65 @@ 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 }
24
36
  # TODO: Add advanced detection of variables that could
25
37
  # have been assigned to an array or a hash.
26
- class Size < Cop
27
- MSG = 'Use `size` instead of `count`.'
28
-
29
- def on_send(node)
30
- return unless eligible_node?(node)
31
-
32
- add_offense(node, location: :selector)
33
- end
34
-
35
- def autocorrect(node)
36
- ->(corrector) { corrector.replace(node.loc.selector, 'size') }
37
- 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
38
+ class Size < Base
39
+ extend AutoCorrector
46
40
 
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
41
+ MSG = 'Use `size` instead of `count`.'
65
42
 
66
- def hash?(node)
67
- return true if node.hash_type?
68
- return false unless node.send_type?
43
+ def_node_matcher :array?, <<~PATTERN
44
+ {
45
+ [!nil? array_type?]
46
+ (send _ :to_a)
47
+ (send (const nil? :Array) :[] _)
48
+ (send nil? :Array _)
49
+ }
50
+ PATTERN
51
+
52
+ def_node_matcher :hash?, <<~PATTERN
53
+ {
54
+ [!nil? hash_type?]
55
+ (send _ :to_h)
56
+ (send (const nil? :Hash) :[] _)
57
+ (send nil? :Hash _)
58
+ }
59
+ PATTERN
60
+
61
+ def_node_matcher :count?, <<~PATTERN
62
+ (send {#array? #hash?} :count)
63
+ PATTERN
69
64
 
70
- _, constant = *node.receiver
65
+ def on_send(node)
66
+ return if node.parent&.block_type? || !count?(node)
71
67
 
72
- constant == :Hash || node.method?(:to_h)
68
+ add_offense(node.loc.selector) do |corrector|
69
+ corrector.replace(node.loc.selector, 'size')
70
+ end
73
71
  end
74
72
  end
75
73
  end
@@ -0,0 +1,45 @@
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 < Base
17
+ include SortBlock
18
+ extend AutoCorrector
19
+
20
+ MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
21
+
22
+ def on_block(node)
23
+ sort_with_block?(node) do |send, var_a, var_b, body|
24
+ replaceable_body?(body, var_b, var_a) do
25
+ range = sort_range(send, node)
26
+
27
+ add_offense(range, message: message(var_a, var_b)) do |corrector|
28
+ replacement = 'sort.reverse'
29
+
30
+ corrector.replace(range, replacement)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def message(var_a, var_b)
39
+ bad_method = "sort { |#{var_a}, #{var_b}| #{var_b} <=> #{var_a} }"
40
+ format(MSG, bad_method: bad_method)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,66 @@
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 < Base
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
25
+
26
+ PREFERRED_METHODS = {
27
+ gsub: :squeeze,
28
+ gsub!: :squeeze!
29
+ }.freeze
30
+
31
+ def_node_matcher :squeeze_candidate?, <<~PATTERN
32
+ (send
33
+ $!nil? ${:gsub :gsub!}
34
+ (regexp
35
+ (str $#repeating_literal?)
36
+ (regopt))
37
+ (str $_))
38
+ PATTERN
39
+
40
+ def on_send(node)
41
+ squeeze_candidate?(node) do |receiver, bad_method, regexp_str, replace_str|
42
+ regexp_str = regexp_str[0..-2] # delete '+' from the end
43
+ regexp_str = interpret_string_escapes(regexp_str)
44
+ return unless replace_str == regexp_str
45
+
46
+ good_method = PREFERRED_METHODS[bad_method]
47
+ message = format(MSG, current: bad_method, prefer: good_method)
48
+
49
+ add_offense(node.loc.selector, message: message) do |corrector|
50
+ string_literal = to_string_literal(replace_str)
51
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
52
+
53
+ corrector.replace(node.source_range, new_code)
54
+ end
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def repeating_literal?(regex_str)
61
+ regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -3,8 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop identifies unnecessary use of a regex where
7
- # `String#start_with?` would suffice.
6
+ # This cop identifies unnecessary use of a regex where `String#start_with?` would suffice.
7
+ #
8
+ # This cop has `SafeMultiline` configuration option that `true` by default because
9
+ # `^start` is unsafe as it will behave incompatible with `start_with?`
10
+ # for receiver is multiline string.
8
11
  #
9
12
  # @example
10
13
  # # bad
@@ -15,6 +18,12 @@ module RuboCop
15
18
  # 'abc'.match(/\Aab/)
16
19
  # /\Aab/.match('abc')
17
20
  #
21
+ # # good
22
+ # 'abc'.start_with?('ab')
23
+ #
24
+ # @example SafeMultiline: true (default)
25
+ #
26
+ # # good
18
27
  # 'abc'.match?(/^ab/)
19
28
  # /^ab/.match?('abc')
20
29
  # 'abc' =~ /^ab/
@@ -22,10 +31,19 @@ module RuboCop
22
31
  # 'abc'.match(/^ab/)
23
32
  # /^ab/.match('abc')
24
33
  #
25
- # # good
26
- # 'abc'.start_with?('ab')
27
- class StartWith < Cop
34
+ # @example SafeMultiline: false
35
+ #
36
+ # # bad
37
+ # 'abc'.match?(/^ab/)
38
+ # /^ab/.match?('abc')
39
+ # 'abc' =~ /^ab/
40
+ # /^ab/ =~ 'abc'
41
+ # 'abc'.match(/^ab/)
42
+ # /^ab/.match('abc')
43
+ #
44
+ class StartWith < Base
28
45
  include RegexpMetacharacter
46
+ extend AutoCorrector
29
47
 
30
48
  MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
31
49
  'the beginning of the string.'
@@ -37,25 +55,19 @@ module RuboCop
37
55
  PATTERN
38
56
 
39
57
  def on_send(node)
40
- return unless redundant_regex?(node)
58
+ return unless (receiver, regex_str = redundant_regex?(node))
41
59
 
42
- add_offense(node)
43
- end
44
- alias on_match_with_lvasgn on_send
45
-
46
- def autocorrect(node)
47
- redundant_regex?(node) do |receiver, regex_str|
60
+ add_offense(node) do |corrector|
48
61
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
49
62
  regex_str = drop_start_metacharacter(regex_str)
50
63
  regex_str = interpret_string_escapes(regex_str)
51
64
 
52
- lambda do |corrector|
53
- new_source = receiver.source + '.start_with?(' +
54
- to_string_literal(regex_str) + ')'
55
- corrector.replace(node.source_range, new_source)
56
- end
65
+ new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})"
66
+
67
+ corrector.replace(node.source_range, new_source)
57
68
  end
58
69
  end
70
+ alias on_match_with_lvasgn on_send
59
71
  end
60
72
  end
61
73
  end