rubocop-performance 1.7.1 → 1.9.2

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/config/default.yml +46 -7
  4. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
  5. data/lib/rubocop/cop/performance/ancestors_include.rb +15 -13
  6. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +77 -0
  7. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +8 -12
  8. data/lib/rubocop/cop/performance/bind_call.rb +9 -18
  9. data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +52 -0
  10. data/lib/rubocop/cop/performance/caller.rb +14 -15
  11. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  12. data/lib/rubocop/cop/performance/casecmp.rb +13 -20
  13. data/lib/rubocop/cop/performance/chain_array_allocation.rb +24 -28
  14. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  15. data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
  16. data/lib/rubocop/cop/performance/constant_regexp.rb +68 -0
  17. data/lib/rubocop/cop/performance/count.rb +14 -16
  18. data/lib/rubocop/cop/performance/delete_prefix.rb +14 -22
  19. data/lib/rubocop/cop/performance/delete_suffix.rb +14 -22
  20. data/lib/rubocop/cop/performance/detect.rb +65 -32
  21. data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
  22. data/lib/rubocop/cop/performance/end_with.rb +9 -13
  23. data/lib/rubocop/cop/performance/fixed_size.rb +2 -1
  24. data/lib/rubocop/cop/performance/flat_map.rb +21 -22
  25. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +15 -14
  26. data/lib/rubocop/cop/performance/io_readlines.rb +27 -42
  27. data/lib/rubocop/cop/performance/method_object_as_block.rb +32 -0
  28. data/lib/rubocop/cop/performance/open_struct.rb +3 -2
  29. data/lib/rubocop/cop/performance/range_include.rb +8 -6
  30. data/lib/rubocop/cop/performance/redundant_block_call.rb +15 -10
  31. data/lib/rubocop/cop/performance/redundant_match.rb +12 -6
  32. data/lib/rubocop/cop/performance/redundant_merge.rb +19 -17
  33. data/lib/rubocop/cop/performance/redundant_sort_block.rb +6 -16
  34. data/lib/rubocop/cop/performance/redundant_string_chars.rb +10 -18
  35. data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
  36. data/lib/rubocop/cop/performance/reverse_each.rb +13 -12
  37. data/lib/rubocop/cop/performance/reverse_first.rb +5 -10
  38. data/lib/rubocop/cop/performance/size.rb +7 -6
  39. data/lib/rubocop/cop/performance/sort_reverse.rb +6 -15
  40. data/lib/rubocop/cop/performance/squeeze.rb +8 -11
  41. data/lib/rubocop/cop/performance/start_with.rb +9 -13
  42. data/lib/rubocop/cop/performance/string_include.rb +11 -14
  43. data/lib/rubocop/cop/performance/string_replacement.rb +24 -27
  44. data/lib/rubocop/cop/performance/sum.rb +236 -0
  45. data/lib/rubocop/cop/performance/times_map.rb +12 -18
  46. data/lib/rubocop/cop/performance/unfreeze_string.rb +20 -2
  47. data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -12
  48. data/lib/rubocop/cop/performance_cops.rb +6 -0
  49. data/lib/rubocop/performance/version.rb +6 -1
  50. metadata +25 -13
@@ -13,29 +13,19 @@ module RuboCop
13
13
  # # good
14
14
  # array.sort
15
15
  #
16
- class RedundantSortBlock < Cop
16
+ class RedundantSortBlock < Base
17
17
  include SortBlock
18
+ extend AutoCorrector
18
19
 
19
20
  MSG = 'Use `sort` instead of `%<bad_method>s`.'
20
21
 
21
22
  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)
23
+ return unless (send, var_a, var_b, body = sort_with_block?(node))
25
24
 
26
- add_offense(
27
- node,
28
- location: range,
29
- message: message(var_a, var_b)
30
- )
31
- end
32
- end
33
- end
25
+ replaceable_body?(body, var_a, var_b) do
26
+ range = sort_range(send, node)
34
27
 
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)
28
+ add_offense(range, message: message(var_a, var_b)) do |corrector|
39
29
  corrector.replace(range, 'sort')
40
30
  end
41
31
  end
@@ -39,41 +39,33 @@ module RuboCop
39
39
  # str.size
40
40
  # str.empty?
41
41
  #
42
- class RedundantStringChars < Cop
42
+ class RedundantStringChars < Base
43
43
  include RangeHelp
44
+ extend AutoCorrector
44
45
 
45
46
  MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
46
- REPLACEABLE_METHODS = %i[[] slice first last take drop length size empty?].freeze
47
+ RESTRICT_ON_SEND = %i[[] slice first last take drop length size empty?].freeze
47
48
 
48
49
  def_node_matcher :redundant_chars_call?, <<~PATTERN
49
- (send $(send _ :chars) $#replaceable_method? $...)
50
+ (send $(send _ :chars) $_ $...)
50
51
  PATTERN
51
52
 
52
53
  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
54
+ return unless (receiver, method, args = redundant_chars_call?(node))
55
+
56
+ range = offense_range(receiver, node)
57
+ message = build_message(method, args)
59
58
 
60
- def autocorrect(node)
61
- redundant_chars_call?(node) do |receiver, method, args|
59
+ add_offense(range, message: message) do |corrector|
62
60
  range = correction_range(receiver, node)
63
61
  replacement = build_good_method(method, args)
64
62
 
65
- lambda do |corrector|
66
- corrector.replace(range, replacement)
67
- end
63
+ corrector.replace(range, replacement)
68
64
  end
69
65
  end
70
66
 
71
67
  private
72
68
 
73
- def replaceable_method?(method_name)
74
- REPLACEABLE_METHODS.include?(method_name)
75
- end
76
-
77
69
  def offense_range(receiver, node)
78
70
  range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
79
71
  end
@@ -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,30 +12,31 @@ 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
- UNDERSCORE = '_'
20
+ RESTRICT_ON_SEND = %i[each].freeze
20
21
 
21
22
  def_node_matcher :reverse_each?, <<~MATCHER
22
- (send $(send _ :reverse) :each)
23
+ (send (send _ :reverse) :each)
23
24
  MATCHER
24
25
 
25
26
  def on_send(node)
26
- reverse_each?(node) do |receiver|
27
- location_of_reverse = receiver.loc.selector.begin_pos
28
- end_location = node.loc.selector.end_pos
27
+ reverse_each?(node) do
28
+ range = offense_range(node)
29
29
 
30
- range = range_between(location_of_reverse, end_location)
31
-
32
- add_offense(node, location: range)
30
+ add_offense(range) do |corrector|
31
+ corrector.replace(range, 'reverse_each')
32
+ end
33
33
  end
34
34
  end
35
35
 
36
- def autocorrect(node)
37
- range = range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
38
- ->(corrector) { corrector.replace(range, UNDERSCORE) }
36
+ private
37
+
38
+ def offense_range(node)
39
+ range_between(node.children.first.loc.selector.begin_pos, node.loc.selector.end_pos)
39
40
  end
40
41
  end
41
42
  end
@@ -16,10 +16,12 @@ 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`.'
24
+ RESTRICT_ON_SEND = %i[first].freeze
23
25
 
24
26
  def_node_matcher :reverse_first_candidate?, <<~PATTERN
25
27
  (send $(send _ :reverse) :first (int _)?)
@@ -30,16 +32,9 @@ module RuboCop
30
32
  range = correction_range(receiver, node)
31
33
  message = build_message(node)
32
34
 
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)
35
+ add_offense(range, message: message) do |corrector|
36
+ replacement = build_good_method(node)
41
37
 
42
- lambda do |corrector|
43
38
  corrector.replace(range, replacement)
44
39
  end
45
40
  end
@@ -35,8 +35,11 @@ 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`.'
42
+ RESTRICT_ON_SEND = %i[count].freeze
40
43
 
41
44
  def_node_matcher :array?, <<~PATTERN
42
45
  {
@@ -63,11 +66,9 @@ module RuboCop
63
66
  def on_send(node)
64
67
  return if node.parent&.block_type? || !count?(node)
65
68
 
66
- add_offense(node, location: :selector)
67
- end
68
-
69
- def autocorrect(node)
70
- ->(corrector) { corrector.replace(node.loc.selector, 'size') }
69
+ add_offense(node.loc.selector) do |corrector|
70
+ corrector.replace(node.loc.selector, 'size')
71
+ end
71
72
  end
72
73
  end
73
74
  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,8 +18,11 @@ 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`.'
25
+ RESTRICT_ON_SEND = %i[gsub gsub!].freeze
23
26
 
24
27
  PREFERRED_METHODS = {
25
28
  gsub: :squeeze,
@@ -36,24 +39,18 @@ module RuboCop
36
39
  PATTERN
37
40
 
38
41
  def on_send(node)
39
- squeeze_candidate?(node) do |_, bad_method, regexp_str, replace_str|
42
+ squeeze_candidate?(node) do |receiver, bad_method, regexp_str, replace_str|
40
43
  regexp_str = regexp_str[0..-2] # delete '+' from the end
41
44
  regexp_str = interpret_string_escapes(regexp_str)
42
45
  return unless replace_str == regexp_str
43
46
 
44
47
  good_method = PREFERRED_METHODS[bad_method]
45
48
  message = format(MSG, current: bad_method, prefer: good_method)
46
- add_offense(node, location: :selector, message: message)
47
- end
48
- end
49
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]
50
+ add_offense(node.loc.selector, message: message) do |corrector|
54
51
  string_literal = to_string_literal(replace_str)
55
-
56
52
  new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
53
+
57
54
  corrector.replace(node.source_range, new_code)
58
55
  end
59
56
  end
@@ -62,7 +59,7 @@ module RuboCop
62
59
  private
63
60
 
64
61
  def repeating_literal?(regex_str)
65
- regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
62
+ regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/o)
66
63
  end
67
64
  end
68
65
  end
@@ -41,11 +41,13 @@ 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.'
50
+ RESTRICT_ON_SEND = %i[match =~ match?].freeze
49
51
 
50
52
  def_node_matcher :redundant_regex?, <<~PATTERN
51
53
  {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt)))
@@ -54,25 +56,19 @@ module RuboCop
54
56
  PATTERN
55
57
 
56
58
  def on_send(node)
57
- return unless redundant_regex?(node)
59
+ return unless (receiver, regex_str = redundant_regex?(node))
58
60
 
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|
61
+ add_offense(node) do |corrector|
65
62
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
66
63
  regex_str = drop_start_metacharacter(regex_str)
67
64
  regex_str = interpret_string_escapes(regex_str)
68
65
 
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
66
+ new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})"
67
+
68
+ corrector.replace(node.source_range, new_source)
74
69
  end
75
70
  end
71
+ alias on_match_with_lvasgn on_send
76
72
  end
77
73
  end
78
74
  end
@@ -19,8 +19,11 @@ 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.'
26
+ RESTRICT_ON_SEND = %i[match =~ match?].freeze
24
27
 
25
28
  def_node_matcher :redundant_regex?, <<~PATTERN
26
29
  {(send $!nil? {:match :=~ :match?} (regexp (str $#literal?) (regopt)))
@@ -29,29 +32,23 @@ module RuboCop
29
32
  PATTERN
30
33
 
31
34
  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
35
+ return unless (receiver, regex_str = redundant_regex?(node))
37
36
 
38
- def autocorrect(node)
39
- redundant_regex?(node) do |receiver, regex_str|
37
+ add_offense(node) do |corrector|
40
38
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
41
39
  regex_str = interpret_string_escapes(regex_str)
42
40
 
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
41
+ new_source = "#{receiver.source}.include?(#{to_string_literal(regex_str)})"
42
+
43
+ corrector.replace(node.source_range, new_source)
48
44
  end
49
45
  end
46
+ alias on_match_with_lvasgn on_send
50
47
 
51
48
  private
52
49
 
53
50
  def literal?(regex_str)
54
- regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/)
51
+ regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/o)
55
52
  end
56
53
  end
57
54
  end
@@ -18,10 +18,12 @@ 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`.'
26
+ RESTRICT_ON_SEND = %i[gsub gsub!].freeze
25
27
  DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
26
28
  DELETE = 'delete'
27
29
  TR = 'tr'
@@ -42,33 +44,37 @@ module RuboCop
42
44
  end
43
45
  end
44
46
 
45
- def autocorrect(node)
47
+ private
48
+
49
+ def offense(node, first_param, second_param)
50
+ first_source, = first_source(first_param)
51
+ first_source = interpret_string_escapes(first_source) unless first_param.str_type?
52
+ second_source, = *second_param
53
+ message = message(node, first_source, second_source)
54
+
55
+ add_offense(range(node), message: message) do |corrector|
56
+ autocorrect(corrector, node)
57
+ end
58
+ end
59
+
60
+ def autocorrect(corrector, node)
46
61
  _string, _method, first_param, second_param = *node
47
62
  first_source, = first_source(first_param)
48
63
  second_source, = *second_param
49
64
 
50
65
  first_source = interpret_string_escapes(first_source) unless first_param.str_type?
51
66
 
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)
67
+ replace_method(corrector, node, first_source, second_source, first_param)
57
68
  end
58
69
 
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
70
+ def replace_method(corrector, node, first_source, second_source, first_param)
71
+ replacement_method = replacement_method(node, first_source, second_source)
66
72
 
67
- remove_second_param(corrector, node, first_param) if second.empty? && first.length == 1
68
- end
69
- end
73
+ corrector.replace(node.loc.selector, replacement_method)
74
+ corrector.replace(first_param.source_range, to_string_literal(first_source)) unless first_param.str_type?
70
75
 
71
- private
76
+ remove_second_param(corrector, node, first_param) if second_source.empty? && first_source.length == 1
77
+ end
72
78
 
73
79
  def accept_second_param?(second_param)
74
80
  second_source, = *second_param
@@ -92,15 +98,6 @@ module RuboCop
92
98
  first_source.length != 1
93
99
  end
94
100
 
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
101
  def first_source(first_param)
105
102
  case first_param.type
106
103
  when :regexp