rubocop-performance 1.7.1 → 1.8.0

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