rubocop-performance 1.7.1 → 1.8.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +18 -7
  3. data/lib/rubocop/cop/performance/ancestors_include.rb +14 -13
  4. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +7 -12
  5. data/lib/rubocop/cop/performance/bind_call.rb +8 -18
  6. data/lib/rubocop/cop/performance/caller.rb +3 -2
  7. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  8. data/lib/rubocop/cop/performance/casecmp.rb +12 -20
  9. data/lib/rubocop/cop/performance/chain_array_allocation.rb +4 -10
  10. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  11. data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
  12. data/lib/rubocop/cop/performance/count.rb +13 -16
  13. data/lib/rubocop/cop/performance/delete_prefix.rb +13 -22
  14. data/lib/rubocop/cop/performance/delete_suffix.rb +13 -22
  15. data/lib/rubocop/cop/performance/detect.rb +29 -26
  16. data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
  17. data/lib/rubocop/cop/performance/end_with.rb +8 -13
  18. data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
  19. data/lib/rubocop/cop/performance/flat_map.rb +20 -22
  20. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +13 -14
  21. data/lib/rubocop/cop/performance/io_readlines.rb +25 -36
  22. data/lib/rubocop/cop/performance/open_struct.rb +2 -2
  23. data/lib/rubocop/cop/performance/range_include.rb +7 -6
  24. data/lib/rubocop/cop/performance/redundant_block_call.rb +11 -6
  25. data/lib/rubocop/cop/performance/redundant_match.rb +11 -6
  26. data/lib/rubocop/cop/performance/redundant_merge.rb +18 -17
  27. data/lib/rubocop/cop/performance/redundant_sort_block.rb +6 -16
  28. data/lib/rubocop/cop/performance/redundant_string_chars.rb +8 -12
  29. data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
  30. data/lib/rubocop/cop/performance/reverse_each.rb +9 -5
  31. data/lib/rubocop/cop/performance/reverse_first.rb +4 -10
  32. data/lib/rubocop/cop/performance/size.rb +6 -6
  33. data/lib/rubocop/cop/performance/sort_reverse.rb +6 -15
  34. data/lib/rubocop/cop/performance/squeeze.rb +6 -10
  35. data/lib/rubocop/cop/performance/start_with.rb +8 -13
  36. data/lib/rubocop/cop/performance/string_include.rb +9 -13
  37. data/lib/rubocop/cop/performance/string_replacement.rb +23 -27
  38. data/lib/rubocop/cop/performance/sum.rb +129 -0
  39. data/lib/rubocop/cop/performance/times_map.rb +11 -18
  40. data/lib/rubocop/cop/performance/unfreeze_string.rb +1 -1
  41. data/lib/rubocop/cop/performance/uri_default_parser.rb +6 -12
  42. data/lib/rubocop/cop/performance_cops.rb +2 -0
  43. data/lib/rubocop/performance/version.rb +1 -1
  44. metadata +6 -4
@@ -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
@@ -16,8 +16,9 @@ module RuboCop
16
16
  # array.last(5).reverse
17
17
  # array.last
18
18
  #
19
- class ReverseFirst < Cop
19
+ class ReverseFirst < Base
20
20
  include RangeHelp
21
+ extend AutoCorrector
21
22
 
22
23
  MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
23
24
 
@@ -30,16 +31,9 @@ module RuboCop
30
31
  range = correction_range(receiver, node)
31
32
  message = build_message(node)
32
33
 
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)
34
+ add_offense(range, message: message) do |corrector|
35
+ replacement = build_good_method(node)
41
36
 
42
- lambda do |corrector|
43
37
  corrector.replace(range, replacement)
44
38
  end
45
39
  end
@@ -35,7 +35,9 @@ module RuboCop
35
35
  # [1, 2, 3].count { |e| e > 2 }
36
36
  # TODO: Add advanced detection of variables that could
37
37
  # have been assigned to an array or a hash.
38
- class Size < Cop
38
+ class Size < Base
39
+ extend AutoCorrector
40
+
39
41
  MSG = 'Use `size` instead of `count`.'
40
42
 
41
43
  def_node_matcher :array?, <<~PATTERN
@@ -63,11 +65,9 @@ module RuboCop
63
65
  def on_send(node)
64
66
  return if node.parent&.block_type? || !count?(node)
65
67
 
66
- add_offense(node, location: :selector)
67
- end
68
-
69
- def autocorrect(node)
70
- ->(corrector) { corrector.replace(node.loc.selector, 'size') }
68
+ add_offense(node.loc.selector) do |corrector|
69
+ corrector.replace(node.loc.selector, 'size')
70
+ end
71
71
  end
72
72
  end
73
73
  end
@@ -13,8 +13,9 @@ module RuboCop
13
13
  # # good
14
14
  # array.sort.reverse
15
15
  #
16
- class SortReverse < Cop
16
+ class SortReverse < Base
17
17
  include SortBlock
18
+ extend AutoCorrector
18
19
 
19
20
  MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
20
21
 
@@ -23,21 +24,11 @@ module RuboCop
23
24
  replaceable_body?(body, var_b, var_a) do
24
25
  range = sort_range(send, node)
25
26
 
26
- add_offense(
27
- node,
28
- location: range,
29
- message: message(var_a, var_b)
30
- )
31
- end
32
- end
33
- end
27
+ add_offense(range, message: message(var_a, var_b)) do |corrector|
28
+ replacement = 'sort.reverse'
34
29
 
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)
30
+ corrector.replace(range, replacement)
31
+ end
41
32
  end
42
33
  end
43
34
  end
@@ -18,7 +18,9 @@ module RuboCop
18
18
  # str.squeeze('a')
19
19
  # str.squeeze!('a')
20
20
  #
21
- class Squeeze < Cop
21
+ class Squeeze < Base
22
+ extend AutoCorrector
23
+
22
24
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
23
25
 
24
26
  PREFERRED_METHODS = {
@@ -36,24 +38,18 @@ module RuboCop
36
38
  PATTERN
37
39
 
38
40
  def on_send(node)
39
- squeeze_candidate?(node) do |_, bad_method, regexp_str, replace_str|
41
+ squeeze_candidate?(node) do |receiver, bad_method, regexp_str, replace_str|
40
42
  regexp_str = regexp_str[0..-2] # delete '+' from the end
41
43
  regexp_str = interpret_string_escapes(regexp_str)
42
44
  return unless replace_str == regexp_str
43
45
 
44
46
  good_method = PREFERRED_METHODS[bad_method]
45
47
  message = format(MSG, current: bad_method, prefer: good_method)
46
- add_offense(node, location: :selector, message: message)
47
- end
48
- end
49
48
 
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]
49
+ add_offense(node.loc.selector, message: message) do |corrector|
54
50
  string_literal = to_string_literal(replace_str)
55
-
56
51
  new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
52
+
57
53
  corrector.replace(node.source_range, new_code)
58
54
  end
59
55
  end
@@ -41,8 +41,9 @@ module RuboCop
41
41
  # 'abc'.match(/^ab/)
42
42
  # /^ab/.match('abc')
43
43
  #
44
- class StartWith < Cop
44
+ class StartWith < Base
45
45
  include RegexpMetacharacter
46
+ extend AutoCorrector
46
47
 
47
48
  MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
48
49
  'the beginning of the string.'
@@ -54,25 +55,19 @@ module RuboCop
54
55
  PATTERN
55
56
 
56
57
  def on_send(node)
57
- return unless redundant_regex?(node)
58
+ return unless (receiver, regex_str = redundant_regex?(node))
58
59
 
59
- add_offense(node)
60
- end
61
- alias on_match_with_lvasgn on_send
62
-
63
- def autocorrect(node)
64
- redundant_regex?(node) do |receiver, regex_str|
60
+ add_offense(node) do |corrector|
65
61
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
66
62
  regex_str = drop_start_metacharacter(regex_str)
67
63
  regex_str = interpret_string_escapes(regex_str)
68
64
 
69
- lambda do |corrector|
70
- new_source = receiver.source + '.start_with?(' +
71
- to_string_literal(regex_str) + ')'
72
- corrector.replace(node.source_range, new_source)
73
- end
65
+ new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})"
66
+
67
+ corrector.replace(node.source_range, new_source)
74
68
  end
75
69
  end
70
+ alias on_match_with_lvasgn on_send
76
71
  end
77
72
  end
78
73
  end
@@ -19,7 +19,9 @@ module RuboCop
19
19
  #
20
20
  # # good
21
21
  # 'abc'.include?('ab')
22
- class StringInclude < Cop
22
+ class StringInclude < Base
23
+ extend AutoCorrector
24
+
23
25
  MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
24
26
 
25
27
  def_node_matcher :redundant_regex?, <<~PATTERN
@@ -29,24 +31,18 @@ module RuboCop
29
31
  PATTERN
30
32
 
31
33
  def on_send(node)
32
- return unless redundant_regex?(node)
33
-
34
- add_offense(node)
35
- end
36
- alias on_match_with_lvasgn on_send
34
+ return unless (receiver, regex_str = redundant_regex?(node))
37
35
 
38
- def autocorrect(node)
39
- redundant_regex?(node) do |receiver, regex_str|
36
+ add_offense(node) do |corrector|
40
37
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
41
38
  regex_str = interpret_string_escapes(regex_str)
42
39
 
43
- lambda do |corrector|
44
- new_source = receiver.source + '.include?(' +
45
- to_string_literal(regex_str) + ')'
46
- corrector.replace(node.source_range, new_source)
47
- end
40
+ new_source = "#{receiver.source}.include?(#{to_string_literal(regex_str)})"
41
+
42
+ corrector.replace(node.source_range, new_source)
48
43
  end
49
44
  end
45
+ alias on_match_with_lvasgn on_send
50
46
 
51
47
  private
52
48
 
@@ -18,8 +18,9 @@ module RuboCop
18
18
  # 'abc'.gsub(/a+/, 'd')
19
19
  # 'abc'.tr('b', 'd')
20
20
  # 'a b c'.delete(' ')
21
- class StringReplacement < Cop
21
+ class StringReplacement < Base
22
22
  include RangeHelp
23
+ extend AutoCorrector
23
24
 
24
25
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
25
26
  DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
@@ -42,33 +43,37 @@ module RuboCop
42
43
  end
43
44
  end
44
45
 
45
- def autocorrect(node)
46
+ private
47
+
48
+ def offense(node, first_param, second_param)
49
+ first_source, = first_source(first_param)
50
+ first_source = interpret_string_escapes(first_source) unless first_param.str_type?
51
+ second_source, = *second_param
52
+ message = message(node, first_source, second_source)
53
+
54
+ add_offense(range(node), message: message) do |corrector|
55
+ autocorrect(corrector, node)
56
+ end
57
+ end
58
+
59
+ def autocorrect(corrector, node)
46
60
  _string, _method, first_param, second_param = *node
47
61
  first_source, = first_source(first_param)
48
62
  second_source, = *second_param
49
63
 
50
64
  first_source = interpret_string_escapes(first_source) unless first_param.str_type?
51
65
 
52
- replacement_method =
53
- replacement_method(node, first_source, second_source)
54
-
55
- replace_method(node, first_source, second_source, first_param,
56
- replacement_method)
66
+ replace_method(corrector, node, first_source, second_source, first_param)
57
67
  end
58
68
 
59
- def replace_method(node, first, second, first_param, replacement)
60
- lambda do |corrector|
61
- corrector.replace(node.loc.selector, replacement)
62
- unless first_param.str_type?
63
- corrector.replace(first_param.source_range,
64
- to_string_literal(first))
65
- end
69
+ def replace_method(corrector, node, first_source, second_source, first_param)
70
+ replacement_method = replacement_method(node, first_source, second_source)
66
71
 
67
- remove_second_param(corrector, node, first_param) if second.empty? && first.length == 1
68
- end
69
- end
72
+ corrector.replace(node.loc.selector, replacement_method)
73
+ corrector.replace(first_param.source_range, to_string_literal(first_source)) unless first_param.str_type?
70
74
 
71
- private
75
+ remove_second_param(corrector, node, first_param) if second_source.empty? && first_source.length == 1
76
+ end
72
77
 
73
78
  def accept_second_param?(second_param)
74
79
  second_source, = *second_param
@@ -92,15 +97,6 @@ module RuboCop
92
97
  first_source.length != 1
93
98
  end
94
99
 
95
- def offense(node, first_param, second_param)
96
- first_source, = first_source(first_param)
97
- first_source = interpret_string_escapes(first_source) unless first_param.str_type?
98
- second_source, = *second_param
99
- message = message(node, first_source, second_source)
100
-
101
- add_offense(node, location: range(node), message: message)
102
- end
103
-
104
100
  def first_source(first_param)
105
101
  case first_param.type
106
102
  when :regexp
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where custom code finding the sum of elements
7
+ # in some Enumerable object can be replaced by `Enumerable#sum` method.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # [1, 2, 3].inject(:+)
12
+ # [1, 2, 3].reduce(10, :+)
13
+ # [1, 2, 3].reduce { |acc, elem| acc + elem }
14
+ #
15
+ # # good
16
+ # [1, 2, 3].sum
17
+ # [1, 2, 3].sum(10)
18
+ # [1, 2, 3].sum
19
+ #
20
+ class Sum < Base
21
+ include RangeHelp
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
25
+
26
+ def_node_matcher :sum_candidate?, <<~PATTERN
27
+ (send _ ${:inject :reduce} $_init ? (sym :+))
28
+ PATTERN
29
+
30
+ def_node_matcher :sum_with_block_candidate?, <<~PATTERN
31
+ (block
32
+ $(send _ {:inject :reduce} $_init ?)
33
+ (args (arg $_acc) (arg $_elem))
34
+ $send)
35
+ PATTERN
36
+
37
+ def_node_matcher :acc_plus_elem?, <<~PATTERN
38
+ (send (lvar %1) :+ (lvar %2))
39
+ PATTERN
40
+ alias elem_plus_acc? acc_plus_elem?
41
+
42
+ def on_send(node)
43
+ sum_candidate?(node) do |method, init|
44
+ range = sum_method_range(node)
45
+ message = build_method_message(method, init)
46
+
47
+ add_offense(range, message: message) do |corrector|
48
+ autocorrect(corrector, init, range)
49
+ end
50
+ end
51
+ end
52
+
53
+ def on_block(node)
54
+ sum_with_block_candidate?(node) do |send, init, var_acc, var_elem, body|
55
+ if acc_plus_elem?(body, var_acc, var_elem) || elem_plus_acc?(body, var_elem, var_acc)
56
+ range = sum_block_range(send, node)
57
+ message = build_block_message(send, init, var_acc, var_elem, body)
58
+
59
+ add_offense(range, message: message) do |corrector|
60
+ autocorrect(corrector, init, range)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def autocorrect(corrector, init, range)
69
+ return if init.empty?
70
+
71
+ replacement = build_good_method(init)
72
+
73
+ corrector.replace(range, replacement)
74
+ end
75
+
76
+ def sum_method_range(node)
77
+ range_between(node.loc.selector.begin_pos, node.loc.end.end_pos)
78
+ end
79
+
80
+ def sum_block_range(send, node)
81
+ range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
82
+ end
83
+
84
+ def build_method_message(method, init)
85
+ good_method = build_good_method(init)
86
+ bad_method = build_method_bad_method(init, method)
87
+ format(MSG, good_method: good_method, bad_method: bad_method)
88
+ end
89
+
90
+ def build_block_message(send, init, var_acc, var_elem, body)
91
+ good_method = build_good_method(init)
92
+ bad_method = build_block_bad_method(send.method_name, init, var_acc, var_elem, body)
93
+ format(MSG, good_method: good_method, bad_method: bad_method)
94
+ end
95
+
96
+ def build_good_method(init)
97
+ good_method = 'sum'
98
+
99
+ unless init.empty?
100
+ init = init.first
101
+ good_method += "(#{init.source})" if init.source.to_i != 0
102
+ end
103
+ good_method
104
+ end
105
+
106
+ def build_method_bad_method(init, method)
107
+ bad_method = "#{method}("
108
+ unless init.empty?
109
+ init = init.first
110
+ bad_method += "#{init.source}, "
111
+ end
112
+ bad_method += ':+)'
113
+ bad_method
114
+ end
115
+
116
+ def build_block_bad_method(method, init, var_acc, var_elem, body)
117
+ bad_method = method.to_s
118
+
119
+ unless init.empty?
120
+ init = init.first
121
+ bad_method += "(#{init.source})"
122
+ end
123
+ bad_method += " { |#{var_acc}, #{var_elem}| #{body.source} }"
124
+ bad_method
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end