rubocop-performance 1.6.1 → 1.9.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/config/default.yml +95 -8
  4. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
  5. data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
  6. data/lib/rubocop/cop/performance/ancestors_include.rb +49 -0
  7. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +74 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +46 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +9 -18
  10. data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +52 -0
  11. data/lib/rubocop/cop/performance/caller.rb +14 -15
  12. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  13. data/lib/rubocop/cop/performance/casecmp.rb +13 -20
  14. data/lib/rubocop/cop/performance/chain_array_allocation.rb +4 -10
  15. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  16. data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
  17. data/lib/rubocop/cop/performance/constant_regexp.rb +68 -0
  18. data/lib/rubocop/cop/performance/count.rb +14 -16
  19. data/lib/rubocop/cop/performance/delete_prefix.rb +14 -22
  20. data/lib/rubocop/cop/performance/delete_suffix.rb +14 -22
  21. data/lib/rubocop/cop/performance/detect.rb +65 -32
  22. data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
  23. data/lib/rubocop/cop/performance/end_with.rb +9 -13
  24. data/lib/rubocop/cop/performance/fixed_size.rb +2 -1
  25. data/lib/rubocop/cop/performance/flat_map.rb +21 -22
  26. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +15 -14
  27. data/lib/rubocop/cop/performance/io_readlines.rb +112 -0
  28. data/lib/rubocop/cop/performance/method_object_as_block.rb +32 -0
  29. data/lib/rubocop/cop/performance/open_struct.rb +3 -2
  30. data/lib/rubocop/cop/performance/range_include.rb +15 -11
  31. data/lib/rubocop/cop/performance/redundant_block_call.rb +15 -10
  32. data/lib/rubocop/cop/performance/redundant_match.rb +12 -6
  33. data/lib/rubocop/cop/performance/redundant_merge.rb +19 -17
  34. data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
  35. data/lib/rubocop/cop/performance/redundant_string_chars.rb +129 -0
  36. data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
  37. data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
  38. data/lib/rubocop/cop/performance/reverse_first.rb +73 -0
  39. data/lib/rubocop/cop/performance/size.rb +42 -43
  40. data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
  41. data/lib/rubocop/cop/performance/squeeze.rb +67 -0
  42. data/lib/rubocop/cop/performance/start_with.rb +9 -13
  43. data/lib/rubocop/cop/performance/string_include.rb +56 -0
  44. data/lib/rubocop/cop/performance/string_replacement.rb +24 -27
  45. data/lib/rubocop/cop/performance/sum.rb +236 -0
  46. data/lib/rubocop/cop/performance/times_map.rb +12 -18
  47. data/lib/rubocop/cop/performance/unfreeze_string.rb +20 -2
  48. data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -12
  49. data/lib/rubocop/cop/performance_cops.rb +16 -0
  50. data/lib/rubocop/performance/version.rb +6 -1
  51. metadata +35 -13
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop finds regular expressions with dynamic components that are all constants.
7
+ #
8
+ # Ruby allocates a new Regexp object every time it executes a code containing such
9
+ # a regular expression. It is more efficient to extract it into a constant
10
+ # or add an `/o` option to perform `#{}` interpolation only once and reuse that
11
+ # Regexp object.
12
+ #
13
+ # @example
14
+ #
15
+ # # bad
16
+ # def tokens(pattern)
17
+ # pattern.scan(TOKEN).reject { |token| token.match?(/\A#{SEPARATORS}\Z/) }
18
+ # end
19
+ #
20
+ # # good
21
+ # ALL_SEPARATORS = /\A#{SEPARATORS}\Z/
22
+ # def tokens(pattern)
23
+ # pattern.scan(TOKEN).reject { |token| token.match?(ALL_SEPARATORS) }
24
+ # end
25
+ #
26
+ # # good
27
+ # def tokens(pattern)
28
+ # pattern.scan(TOKEN).reject { |token| token.match?(/\A#{SEPARATORS}\Z/o) }
29
+ # end
30
+ #
31
+ class ConstantRegexp < Base
32
+ extend AutoCorrector
33
+
34
+ MSG = 'Extract this regexp into a constant or append an `/o` option to its options.'
35
+
36
+ def on_regexp(node)
37
+ return if within_const_assignment?(node) ||
38
+ !include_interpolated_const?(node) ||
39
+ node.single_interpolation?
40
+
41
+ add_offense(node) do |corrector|
42
+ corrector.insert_after(node, 'o')
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def within_const_assignment?(node)
49
+ node.each_ancestor(:casgn).any?
50
+ end
51
+
52
+ def_node_matcher :regexp_escape?, <<~PATTERN
53
+ (send
54
+ (const nil? :Regexp) :escape const_type?)
55
+ PATTERN
56
+
57
+ def include_interpolated_const?(node)
58
+ return false unless node.interpolation?
59
+
60
+ node.each_child_node(:begin).all? do |begin_node|
61
+ inner_node = begin_node.children.first
62
+ inner_node && (inner_node.const_type? || regexp_escape?(inner_node))
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -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?