rubocop-performance 1.7.0 → 1.9.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/config/default.yml +49 -7
  4. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
  5. data/lib/rubocop/cop/performance/ancestors_include.rb +16 -12
  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 +17 -14
  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 +10 -5
  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 +13 -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
@@ -4,7 +4,7 @@ module RuboCop
4
4
  module Cop
5
5
  module Performance
6
6
  # This cop is used to identify usages of `count` on an `Enumerable` that
7
- # follow calls to `select` or `reject`. Querying logic can instead be
7
+ # follow calls to `select`, `find_all`, `filter` or `reject`. Querying logic can instead be
8
8
  # passed to the `count` call.
9
9
  #
10
10
  # @example
@@ -37,15 +37,17 @@ module RuboCop
37
37
  # becomes:
38
38
  #
39
39
  # `Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }`
40
- class Count < Cop
40
+ class Count < Base
41
41
  include RangeHelp
42
+ extend AutoCorrector
42
43
 
43
44
  MSG = 'Use `count` instead of `%<selector>s...%<counter>s`.'
45
+ RESTRICT_ON_SEND = %i[count length size].freeze
44
46
 
45
47
  def_node_matcher :count_candidate?, <<~PATTERN
46
48
  {
47
- (send (block $(send _ ${:select :reject}) ...) ${:count :length :size})
48
- (send $(send _ ${:select :reject} (:block_pass _)) ${:count :length :size})
49
+ (send (block $(send _ ${:select :filter :find_all :reject}) ...) ${:count :length :size})
50
+ (send $(send _ ${:select :filter :find_all :reject} (:block_pass _)) ${:count :length :size})
49
51
  }
50
52
  PATTERN
51
53
 
@@ -57,29 +59,25 @@ module RuboCop
57
59
  selector_node.loc.selector.begin_pos
58
60
  end
59
61
 
60
- add_offense(node,
61
- location: range,
62
- message: format(MSG, selector: selector,
63
- counter: counter))
62
+ add_offense(range, message: format(MSG, selector: selector, counter: counter)) do |corrector|
63
+ autocorrect(corrector, node, selector_node, selector)
64
+ end
64
65
  end
65
66
  end
66
67
 
67
- def autocorrect(node)
68
- selector_node, selector, _counter = count_candidate?(node)
68
+ private
69
+
70
+ def autocorrect(corrector, node, selector_node, selector)
69
71
  selector_loc = selector_node.loc.selector
70
72
 
71
73
  return if selector == :reject
72
74
 
73
75
  range = source_starting_at(node) { |n| n.loc.dot.begin_pos }
74
76
 
75
- lambda do |corrector|
76
- corrector.remove(range)
77
- corrector.replace(selector_loc, 'count')
78
- end
77
+ corrector.remove(range)
78
+ corrector.replace(selector_loc, 'count')
79
79
  end
80
80
 
81
- private
82
-
83
81
  def eligible_node?(node)
84
82
  !(node.parent && node.parent.block_type?)
85
83
  end
@@ -43,13 +43,15 @@ module RuboCop
43
43
  # str.sub(/^prefix/, '')
44
44
  # str.sub!(/^prefix/, '')
45
45
  #
46
- class DeletePrefix < Cop
47
- extend TargetRubyVersion
46
+ class DeletePrefix < Base
48
47
  include RegexpMetacharacter
48
+ extend AutoCorrector
49
+ extend TargetRubyVersion
49
50
 
50
51
  minimum_target_ruby_version 2.5
51
52
 
52
53
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
54
+ RESTRICT_ON_SEND = %i[gsub gsub! sub sub!].freeze
53
55
 
54
56
  PREFERRED_METHODS = {
55
57
  gsub: :delete_prefix,
@@ -63,31 +65,21 @@ module RuboCop
63
65
  PATTERN
64
66
 
65
67
  def on_send(node)
66
- delete_prefix_candidate?(node) do |_, bad_method, _, replace_string|
67
- return unless replace_string.blank?
68
-
69
- good_method = PREFERRED_METHODS[bad_method]
68
+ return unless (receiver, bad_method, regexp_str, replace_string = delete_prefix_candidate?(node))
69
+ return unless replace_string.blank?
70
70
 
71
- message = format(MSG, current: bad_method, prefer: good_method)
71
+ good_method = PREFERRED_METHODS[bad_method]
72
72
 
73
- add_offense(node, location: :selector, message: message)
74
- end
75
- end
73
+ message = format(MSG, current: bad_method, prefer: good_method)
76
74
 
77
- def autocorrect(node)
78
- delete_prefix_candidate?(node) do |receiver, bad_method, regexp_str, _|
79
- lambda do |corrector|
80
- good_method = PREFERRED_METHODS[bad_method]
81
- regexp_str = drop_start_metacharacter(regexp_str)
82
- regexp_str = interpret_string_escapes(regexp_str)
83
- string_literal = to_string_literal(regexp_str)
75
+ add_offense(node.loc.selector, message: message) do |corrector|
76
+ regexp_str = drop_start_metacharacter(regexp_str)
77
+ regexp_str = interpret_string_escapes(regexp_str)
78
+ string_literal = to_string_literal(regexp_str)
84
79
 
85
- new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
80
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
86
81
 
87
- # TODO: `source_range` is no longer required when RuboCop 0.81 or lower support will be dropped.
88
- # https://github.com/rubocop-hq/rubocop/commit/82eb350d2cba16
89
- corrector.replace(node.source_range, new_code)
90
- end
82
+ corrector.replace(node, new_code)
91
83
  end
92
84
  end
93
85
  end
@@ -43,13 +43,15 @@ module RuboCop
43
43
  # str.sub(/suffix$/, '')
44
44
  # str.sub!(/suffix$/, '')
45
45
  #
46
- class DeleteSuffix < Cop
47
- extend TargetRubyVersion
46
+ class DeleteSuffix < Base
48
47
  include RegexpMetacharacter
48
+ extend AutoCorrector
49
+ extend TargetRubyVersion
49
50
 
50
51
  minimum_target_ruby_version 2.5
51
52
 
52
53
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
54
+ RESTRICT_ON_SEND = %i[gsub gsub! sub sub!].freeze
53
55
 
54
56
  PREFERRED_METHODS = {
55
57
  gsub: :delete_suffix,
@@ -63,31 +65,21 @@ module RuboCop
63
65
  PATTERN
64
66
 
65
67
  def on_send(node)
66
- delete_suffix_candidate?(node) do |_, bad_method, _, replace_string|
67
- return unless replace_string.blank?
68
-
69
- good_method = PREFERRED_METHODS[bad_method]
68
+ return unless (receiver, bad_method, regexp_str, replace_string = delete_suffix_candidate?(node))
69
+ return unless replace_string.blank?
70
70
 
71
- message = format(MSG, current: bad_method, prefer: good_method)
71
+ good_method = PREFERRED_METHODS[bad_method]
72
72
 
73
- add_offense(node, location: :selector, message: message)
74
- end
75
- end
73
+ message = format(MSG, current: bad_method, prefer: good_method)
76
74
 
77
- def autocorrect(node)
78
- delete_suffix_candidate?(node) do |receiver, bad_method, regexp_str, _|
79
- lambda do |corrector|
80
- good_method = PREFERRED_METHODS[bad_method]
81
- regexp_str = drop_end_metacharacter(regexp_str)
82
- regexp_str = interpret_string_escapes(regexp_str)
83
- string_literal = to_string_literal(regexp_str)
75
+ add_offense(node.loc.selector, message: message) do |corrector|
76
+ regexp_str = drop_end_metacharacter(regexp_str)
77
+ regexp_str = interpret_string_escapes(regexp_str)
78
+ string_literal = to_string_literal(regexp_str)
84
79
 
85
- new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
80
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
86
81
 
87
- # TODO: `source_range` is no longer required when RuboCop 0.81 or lower support will be dropped.
88
- # https://github.com/rubocop-hq/rubocop/commit/82eb350d2cba16
89
- corrector.replace(node.source_range, new_code)
90
- end
82
+ corrector.replace(node, new_code)
91
83
  end
92
84
  end
93
85
  end
@@ -3,9 +3,9 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop is used to identify usages of
7
- # `select.first`, `select.last`, `find_all.first`, and `find_all.last`
8
- # and change them to use `detect` instead.
6
+ # This cop is used to identify usages of `first`, `last`, `[0]` or `[-1]`
7
+ # chained to `select`, `find_all` or `filter` and change them to use
8
+ # `detect` instead.
9
9
  #
10
10
  # @example
11
11
  # # bad
@@ -13,6 +13,10 @@ module RuboCop
13
13
  # [].select { |item| true }.last
14
14
  # [].find_all { |item| true }.first
15
15
  # [].find_all { |item| true }.last
16
+ # [].filter { |item| true }.first
17
+ # [].filter { |item| true }.last
18
+ # [].filter { |item| true }[0]
19
+ # [].filter { |item| true }[-1]
16
20
  #
17
21
  # # good
18
22
  # [].detect { |item| true }
@@ -22,47 +26,44 @@ module RuboCop
22
26
  # `ActiveRecord` does not implement a `detect` method and `find` has its
23
27
  # own meaning. Correcting ActiveRecord methods with this cop should be
24
28
  # considered unsafe.
25
- class Detect < Cop
29
+ class Detect < Base
30
+ extend AutoCorrector
31
+
32
+ CANDIDATE_METHODS = Set[:select, :find_all, :filter].freeze
33
+
26
34
  MSG = 'Use `%<prefer>s` instead of ' \
27
35
  '`%<first_method>s.%<second_method>s`.'
28
36
  REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
29
37
  '`%<first_method>s.%<second_method>s`.'
38
+ INDEX_MSG = 'Use `%<prefer>s` instead of ' \
39
+ '`%<first_method>s[%<index>i]`.'
40
+ INDEX_REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
41
+ '`%<first_method>s[%<index>i]`.'
42
+ RESTRICT_ON_SEND = %i[first last []].freeze
30
43
 
31
44
  def_node_matcher :detect_candidate?, <<~PATTERN
32
45
  {
33
- (send $(block (send _ {:select :find_all}) ...) ${:first :last} $...)
34
- (send $(send _ {:select :find_all} ...) ${:first :last} $...)
46
+ (send $(block (send _ %CANDIDATE_METHODS) ...) ${:first :last} $...)
47
+ (send $(block (send _ %CANDIDATE_METHODS) ...) $:[] (int ${0 -1}))
48
+ (send $(send _ %CANDIDATE_METHODS ...) ${:first :last} $...)
49
+ (send $(send _ %CANDIDATE_METHODS ...) $:[] (int ${0 -1}))
35
50
  }
36
51
  PATTERN
37
52
 
38
53
  def on_send(node)
39
54
  detect_candidate?(node) do |receiver, second_method, args|
55
+ if second_method == :[]
56
+ index = args
57
+ args = {}
58
+ end
59
+
40
60
  return unless args.empty?
41
61
  return unless receiver
42
62
 
43
63
  receiver, _args, body = *receiver if receiver.block_type?
44
64
  return if accept_first_call?(receiver, body)
45
65
 
46
- register_offense(node, receiver, second_method)
47
- end
48
- end
49
-
50
- def autocorrect(node)
51
- receiver, first_method = *node
52
-
53
- replacement = if first_method == :last
54
- "reverse.#{preferred_method}"
55
- else
56
- preferred_method
57
- end
58
-
59
- first_range = receiver.source_range.end.join(node.loc.selector)
60
-
61
- receiver, _args, _body = *receiver if receiver.block_type?
62
-
63
- lambda do |corrector|
64
- corrector.remove(first_range)
65
- corrector.replace(receiver.loc.selector, replacement)
66
+ register_offense(node, receiver, second_method, index)
66
67
  end
67
68
  end
68
69
 
@@ -77,21 +78,53 @@ module RuboCop
77
78
  lazy?(caller)
78
79
  end
79
80
 
80
- def register_offense(node, receiver, second_method)
81
+ def register_offense(node, receiver, second_method, index)
81
82
  _caller, first_method, _args = *receiver
82
83
  range = receiver.loc.selector.join(node.loc.selector)
83
84
 
84
- message = second_method == :last ? REVERSE_MSG : MSG
85
+ message = message_for_method(second_method, index)
85
86
  formatted_message = format(message, prefer: preferred_method,
86
87
  first_method: first_method,
87
- second_method: second_method)
88
+ second_method: second_method,
89
+ index: index)
90
+
91
+ add_offense(range, message: formatted_message) do |corrector|
92
+ autocorrect(corrector, node, replacement(second_method, index))
93
+ end
94
+ end
88
95
 
89
- add_offense(node, location: range, message: formatted_message)
96
+ def replacement(method, index)
97
+ if method == :last || method == :[] && index == -1
98
+ "reverse.#{preferred_method}"
99
+ else
100
+ preferred_method
101
+ end
102
+ end
103
+
104
+ def autocorrect(corrector, node, replacement)
105
+ receiver, _first_method = *node
106
+
107
+ first_range = receiver.source_range.end.join(node.loc.selector)
108
+
109
+ receiver, _args, _body = *receiver if receiver.block_type?
110
+
111
+ corrector.remove(first_range)
112
+ corrector.replace(receiver.loc.selector, replacement)
113
+ end
114
+
115
+ def message_for_method(method, index)
116
+ case method
117
+ when :[]
118
+ index == -1 ? INDEX_REVERSE_MSG : INDEX_MSG
119
+ when :last
120
+ REVERSE_MSG
121
+ else
122
+ MSG
123
+ end
90
124
  end
91
125
 
92
126
  def preferred_method
93
- config.for_cop('Style/CollectionMethods') \
94
- ['PreferredMethods']['detect'] || 'detect'
127
+ config.for_cop('Style/CollectionMethods')['PreferredMethods']['detect'] || 'detect'
95
128
  end
96
129
 
97
130
  def lazy?(node)
@@ -17,39 +17,34 @@ module RuboCop
17
17
  # str.start_with?("a", Some::CONST)
18
18
  # str.start_with?("a", "b", "c")
19
19
  # str.end_with?(var1, var2)
20
- class DoubleStartEndWith < Cop
20
+ class DoubleStartEndWith < Base
21
+ extend AutoCorrector
22
+
21
23
  MSG = 'Use `%<receiver>s.%<method>s(%<combined_args>s)` ' \
22
24
  'instead of `%<original_code>s`.'
23
25
 
24
26
  def on_or(node)
25
- receiver,
26
- method,
27
- first_call_args,
28
- second_call_args = process_source(node)
27
+ receiver, method, first_call_args, second_call_args = process_source(node)
29
28
 
30
29
  return unless receiver && second_call_args.all?(&:pure?)
31
30
 
32
31
  combined_args = combine_args(first_call_args, second_call_args)
33
32
 
34
- add_offense_for_double_call(node, receiver, method, combined_args)
33
+ add_offense(node, message: message(node, receiver, method, combined_args)) do |corrector|
34
+ autocorrect(corrector, first_call_args, second_call_args, combined_args)
35
+ end
35
36
  end
36
37
 
37
- def autocorrect(node)
38
- _receiver, _method,
39
- first_call_args, second_call_args = process_source(node)
38
+ private
40
39
 
41
- combined_args = combine_args(first_call_args, second_call_args)
40
+ def autocorrect(corrector, first_call_args, second_call_args, combined_args)
42
41
  first_argument = first_call_args.first.loc.expression
43
42
  last_argument = second_call_args.last.loc.expression
44
43
  range = first_argument.join(last_argument)
45
44
 
46
- lambda do |corrector|
47
- corrector.replace(range, combined_args)
48
- end
45
+ corrector.replace(range, combined_args)
49
46
  end
50
47
 
51
- private
52
-
53
48
  def process_source(node)
54
49
  if check_for_active_support_aliases?
55
50
  check_with_active_support_aliases(node)
@@ -58,17 +53,14 @@ module RuboCop
58
53
  end
59
54
  end
60
55
 
61
- def combine_args(first_call_args, second_call_args)
62
- (first_call_args + second_call_args).map(&:source).join(', ')
56
+ def message(node, receiver, method, combined_args)
57
+ format(
58
+ MSG, receiver: receiver.source, method: method, combined_args: combined_args, original_code: node.source
59
+ )
63
60
  end
64
61
 
65
- def add_offense_for_double_call(node, receiver, method, combined_args)
66
- msg = format(MSG, receiver: receiver.source,
67
- method: method,
68
- combined_args: combined_args,
69
- original_code: node.source)
70
-
71
- add_offense(node, message: msg)
62
+ def combine_args(first_call_args, second_call_args)
63
+ (first_call_args + second_call_args).map(&:source).join(', ')
72
64
  end
73
65
 
74
66
  def check_for_active_support_aliases?
@@ -41,11 +41,13 @@ module RuboCop
41
41
  # 'abc'.match(/bc$/)
42
42
  # /bc$/.match('abc')
43
43
  #
44
- class EndWith < Cop
44
+ class EndWith < Base
45
45
  include RegexpMetacharacter
46
+ extend AutoCorrector
46
47
 
47
48
  MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
48
49
  'the end 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_end?) (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_end_metacharacter(regex_str)
67
64
  regex_str = interpret_string_escapes(regex_str)
68
65
 
69
- lambda do |corrector|
70
- new_source = receiver.source + '.end_with?(' +
71
- to_string_literal(regex_str) + ')'
72
- corrector.replace(node.source_range, new_source)
73
- end
66
+ new_source = "#{receiver.source}.end_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