rubocop-performance 1.5.2 → 1.8.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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +5 -1
  4. data/config/default.yml +96 -13
  5. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +76 -0
  6. data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
  7. data/lib/rubocop/cop/performance/ancestors_include.rb +48 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +45 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +77 -0
  10. data/lib/rubocop/cop/performance/caller.rb +5 -4
  11. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  12. data/lib/rubocop/cop/performance/casecmp.rb +17 -23
  13. data/lib/rubocop/cop/performance/chain_array_allocation.rb +5 -11
  14. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  15. data/lib/rubocop/cop/performance/compare_with_block.rb +12 -23
  16. data/lib/rubocop/cop/performance/count.rb +14 -17
  17. data/lib/rubocop/cop/performance/delete_prefix.rb +87 -0
  18. data/lib/rubocop/cop/performance/delete_suffix.rb +87 -0
  19. data/lib/rubocop/cop/performance/detect.rb +64 -32
  20. data/lib/rubocop/cop/performance/double_start_end_with.rb +18 -26
  21. data/lib/rubocop/cop/performance/end_with.rb +38 -25
  22. data/lib/rubocop/cop/performance/fixed_size.rb +2 -2
  23. data/lib/rubocop/cop/performance/flat_map.rb +21 -23
  24. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +14 -15
  25. data/lib/rubocop/cop/performance/io_readlines.rb +116 -0
  26. data/lib/rubocop/cop/performance/open_struct.rb +3 -3
  27. data/lib/rubocop/cop/performance/range_include.rb +15 -12
  28. data/lib/rubocop/cop/performance/redundant_block_call.rb +14 -9
  29. data/lib/rubocop/cop/performance/redundant_match.rb +13 -8
  30. data/lib/rubocop/cop/performance/redundant_merge.rb +36 -23
  31. data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
  32. data/lib/rubocop/cop/performance/redundant_string_chars.rb +133 -0
  33. data/lib/rubocop/cop/performance/regexp_match.rb +32 -32
  34. data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
  35. data/lib/rubocop/cop/performance/reverse_first.rb +72 -0
  36. data/lib/rubocop/cop/performance/size.rb +41 -43
  37. data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
  38. data/lib/rubocop/cop/performance/squeeze.rb +66 -0
  39. data/lib/rubocop/cop/performance/start_with.rb +38 -28
  40. data/lib/rubocop/cop/performance/string_include.rb +55 -0
  41. data/lib/rubocop/cop/performance/string_replacement.rb +25 -36
  42. data/lib/rubocop/cop/performance/sum.rb +134 -0
  43. data/lib/rubocop/cop/performance/times_map.rb +12 -19
  44. data/lib/rubocop/cop/performance/unfreeze_string.rb +4 -8
  45. data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -13
  46. data/lib/rubocop/cop/performance_cops.rb +17 -0
  47. data/lib/rubocop/performance/inject.rb +1 -1
  48. data/lib/rubocop/performance/version.rb +1 -1
  49. metadata +41 -11
@@ -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,16 @@ 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`.'
44
45
 
45
- def_node_matcher :count_candidate?, <<-PATTERN
46
+ def_node_matcher :count_candidate?, <<~PATTERN
46
47
  {
47
- (send (block $(send _ ${:select :reject}) ...) ${:count :length :size})
48
- (send $(send _ ${:select :reject} (:block_pass _)) ${:count :length :size})
48
+ (send (block $(send _ ${:select :filter :find_all :reject}) ...) ${:count :length :size})
49
+ (send $(send _ ${:select :filter :find_all :reject} (:block_pass _)) ${:count :length :size})
49
50
  }
50
51
  PATTERN
51
52
 
@@ -57,29 +58,25 @@ module RuboCop
57
58
  selector_node.loc.selector.begin_pos
58
59
  end
59
60
 
60
- add_offense(node,
61
- location: range,
62
- message: format(MSG, selector: selector,
63
- counter: counter))
61
+ add_offense(range, message: format(MSG, selector: selector, counter: counter)) do |corrector|
62
+ autocorrect(corrector, node, selector_node, selector)
63
+ end
64
64
  end
65
65
  end
66
66
 
67
- def autocorrect(node)
68
- selector_node, selector, _counter = count_candidate?(node)
67
+ private
68
+
69
+ def autocorrect(corrector, node, selector_node, selector)
69
70
  selector_loc = selector_node.loc.selector
70
71
 
71
72
  return if selector == :reject
72
73
 
73
74
  range = source_starting_at(node) { |n| n.loc.dot.begin_pos }
74
75
 
75
- lambda do |corrector|
76
- corrector.remove(range)
77
- corrector.replace(selector_loc, 'count')
78
- end
76
+ corrector.remove(range)
77
+ corrector.replace(selector_loc, 'count')
79
78
  end
80
79
 
81
- private
82
-
83
80
  def eligible_node?(node)
84
81
  !(node.parent && node.parent.block_type?)
85
82
  end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # In Ruby 2.5, `String#delete_prefix` has been added.
7
+ #
8
+ # This cop identifies places where `gsub(/\Aprefix/, '')` and `sub(/\Aprefix/, '')`
9
+ # can be replaced by `delete_prefix('prefix')`.
10
+ #
11
+ # This cop has `SafeMultiline` configuration option that `true` by default because
12
+ # `^prefix` is unsafe as it will behave incompatible with `delete_prefix`
13
+ # for receiver is multiline string.
14
+ #
15
+ # The `delete_prefix('prefix')` method is faster than `gsub(/\Aprefix/, '')`.
16
+ #
17
+ # @example
18
+ #
19
+ # # bad
20
+ # str.gsub(/\Aprefix/, '')
21
+ # str.gsub!(/\Aprefix/, '')
22
+ #
23
+ # str.sub(/\Aprefix/, '')
24
+ # str.sub!(/\Aprefix/, '')
25
+ #
26
+ # # good
27
+ # str.delete_prefix('prefix')
28
+ # str.delete_prefix!('prefix')
29
+ #
30
+ # @example SafeMultiline: true (default)
31
+ #
32
+ # # good
33
+ # str.gsub(/^prefix/, '')
34
+ # str.gsub!(/^prefix/, '')
35
+ # str.sub(/^prefix/, '')
36
+ # str.sub!(/^prefix/, '')
37
+ #
38
+ # @example SafeMultiline: false
39
+ #
40
+ # # bad
41
+ # str.gsub(/^prefix/, '')
42
+ # str.gsub!(/^prefix/, '')
43
+ # str.sub(/^prefix/, '')
44
+ # str.sub!(/^prefix/, '')
45
+ #
46
+ class DeletePrefix < Base
47
+ include RegexpMetacharacter
48
+ extend AutoCorrector
49
+ extend TargetRubyVersion
50
+
51
+ minimum_target_ruby_version 2.5
52
+
53
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
54
+
55
+ PREFERRED_METHODS = {
56
+ gsub: :delete_prefix,
57
+ gsub!: :delete_prefix!,
58
+ sub: :delete_prefix,
59
+ sub!: :delete_prefix!
60
+ }.freeze
61
+
62
+ def_node_matcher :delete_prefix_candidate?, <<~PATTERN
63
+ (send $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_start?) (regopt)) (str $_))
64
+ PATTERN
65
+
66
+ def on_send(node)
67
+ return unless (receiver, bad_method, regexp_str, replace_string = delete_prefix_candidate?(node))
68
+ return unless replace_string.blank?
69
+
70
+ good_method = PREFERRED_METHODS[bad_method]
71
+
72
+ message = format(MSG, current: bad_method, prefer: good_method)
73
+
74
+ add_offense(node.loc.selector, message: message) do |corrector|
75
+ regexp_str = drop_start_metacharacter(regexp_str)
76
+ regexp_str = interpret_string_escapes(regexp_str)
77
+ string_literal = to_string_literal(regexp_str)
78
+
79
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
80
+
81
+ corrector.replace(node, new_code)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # In Ruby 2.5, `String#delete_suffix` has been added.
7
+ #
8
+ # This cop identifies places where `gsub(/suffix\z/, '')` and `sub(/suffix\z/, '')`
9
+ # can be replaced by `delete_suffix('suffix')`.
10
+ #
11
+ # This cop has `SafeMultiline` configuration option that `true` by default because
12
+ # `suffix$` is unsafe as it will behave incompatible with `delete_suffix?`
13
+ # for receiver is multiline string.
14
+ #
15
+ # The `delete_suffix('suffix')` method is faster than `gsub(/suffix\z/, '')`.
16
+ #
17
+ # @example
18
+ #
19
+ # # bad
20
+ # str.gsub(/suffix\z/, '')
21
+ # str.gsub!(/suffix\z/, '')
22
+ #
23
+ # str.sub(/suffix\z/, '')
24
+ # str.sub!(/suffix\z/, '')
25
+ #
26
+ # # good
27
+ # str.delete_suffix('suffix')
28
+ # str.delete_suffix!('suffix')
29
+ #
30
+ # @example SafeMultiline: true (default)
31
+ #
32
+ # # good
33
+ # str.gsub(/suffix$/, '')
34
+ # str.gsub!(/suffix$/, '')
35
+ # str.sub(/suffix$/, '')
36
+ # str.sub!(/suffix$/, '')
37
+ #
38
+ # @example SafeMultiline: false
39
+ #
40
+ # # bad
41
+ # str.gsub(/suffix$/, '')
42
+ # str.gsub!(/suffix$/, '')
43
+ # str.sub(/suffix$/, '')
44
+ # str.sub!(/suffix$/, '')
45
+ #
46
+ class DeleteSuffix < Base
47
+ include RegexpMetacharacter
48
+ extend AutoCorrector
49
+ extend TargetRubyVersion
50
+
51
+ minimum_target_ruby_version 2.5
52
+
53
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
54
+
55
+ PREFERRED_METHODS = {
56
+ gsub: :delete_suffix,
57
+ gsub!: :delete_suffix!,
58
+ sub: :delete_suffix,
59
+ sub!: :delete_suffix!
60
+ }.freeze
61
+
62
+ def_node_matcher :delete_suffix_candidate?, <<~PATTERN
63
+ (send $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_end?) (regopt)) (str $_))
64
+ PATTERN
65
+
66
+ def on_send(node)
67
+ return unless (receiver, bad_method, regexp_str, replace_string = delete_suffix_candidate?(node))
68
+ return unless replace_string.blank?
69
+
70
+ good_method = PREFERRED_METHODS[bad_method]
71
+
72
+ message = format(MSG, current: bad_method, prefer: good_method)
73
+
74
+ add_offense(node.loc.selector, message: message) do |corrector|
75
+ regexp_str = drop_end_metacharacter(regexp_str)
76
+ regexp_str = interpret_string_escapes(regexp_str)
77
+ string_literal = to_string_literal(regexp_str)
78
+
79
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
80
+
81
+ corrector.replace(node, new_code)
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -3,8 +3,8 @@
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`
6
+ # This cop is used to identify usages of `first`, `last`, `[0]` or `[-1]`
7
+ # chained to `select`, `find_all`, or `find_all`
8
8
  # and change them to use `detect` instead.
9
9
  #
10
10
  # @example
@@ -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,43 @@ 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]`.'
30
42
 
31
- def_node_matcher :detect_candidate?, <<-PATTERN
43
+ def_node_matcher :detect_candidate?, <<~PATTERN
32
44
  {
33
- (send $(block (send _ {:select :find_all}) ...) ${:first :last} $...)
34
- (send $(send _ {:select :find_all} ...) ${:first :last} $...)
45
+ (send $(block (send _ %CANDIDATE_METHODS) ...) ${:first :last} $...)
46
+ (send $(block (send _ %CANDIDATE_METHODS) ...) $:[] (int ${0 -1}))
47
+ (send $(send _ %CANDIDATE_METHODS ...) ${:first :last} $...)
48
+ (send $(send _ %CANDIDATE_METHODS ...) $:[] (int ${0 -1}))
35
49
  }
36
50
  PATTERN
37
51
 
38
52
  def on_send(node)
39
53
  detect_candidate?(node) do |receiver, second_method, args|
54
+ if second_method == :[]
55
+ index = args
56
+ args = {}
57
+ end
58
+
40
59
  return unless args.empty?
41
60
  return unless receiver
42
61
 
43
62
  receiver, _args, body = *receiver if receiver.block_type?
44
63
  return if accept_first_call?(receiver, body)
45
64
 
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)
65
+ register_offense(node, receiver, second_method, index)
66
66
  end
67
67
  end
68
68
 
@@ -77,21 +77,53 @@ module RuboCop
77
77
  lazy?(caller)
78
78
  end
79
79
 
80
- def register_offense(node, receiver, second_method)
80
+ def register_offense(node, receiver, second_method, index)
81
81
  _caller, first_method, _args = *receiver
82
82
  range = receiver.loc.selector.join(node.loc.selector)
83
83
 
84
- message = second_method == :last ? REVERSE_MSG : MSG
84
+ message = message_for_method(second_method, index)
85
85
  formatted_message = format(message, prefer: preferred_method,
86
86
  first_method: first_method,
87
- second_method: second_method)
87
+ second_method: second_method,
88
+ index: index)
89
+
90
+ add_offense(range, message: formatted_message) do |corrector|
91
+ autocorrect(corrector, node, replacement(second_method, index))
92
+ end
93
+ end
88
94
 
89
- add_offense(node, location: range, message: formatted_message)
95
+ def replacement(method, index)
96
+ if method == :last || method == :[] && index == -1
97
+ "reverse.#{preferred_method}"
98
+ else
99
+ preferred_method
100
+ end
101
+ end
102
+
103
+ def autocorrect(corrector, node, replacement)
104
+ receiver, _first_method = *node
105
+
106
+ first_range = receiver.source_range.end.join(node.loc.selector)
107
+
108
+ receiver, _args, _body = *receiver if receiver.block_type?
109
+
110
+ corrector.remove(first_range)
111
+ corrector.replace(receiver.loc.selector, replacement)
112
+ end
113
+
114
+ def message_for_method(method, index)
115
+ case method
116
+ when :[]
117
+ index == -1 ? INDEX_REVERSE_MSG : INDEX_MSG
118
+ when :last
119
+ REVERSE_MSG
120
+ else
121
+ MSG
122
+ end
90
123
  end
91
124
 
92
125
  def preferred_method
93
- config.for_cop('Style/CollectionMethods') \
94
- ['PreferredMethods']['detect'] || 'detect'
126
+ config.for_cop('Style/CollectionMethods')['PreferredMethods']['detect'] || 'detect'
95
127
  end
96
128
 
97
129
  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,30 +53,27 @@ 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?
75
67
  cop_config['IncludeActiveSupportAliases']
76
68
  end
77
69
 
78
- def_node_matcher :two_start_end_with_calls, <<-PATTERN
70
+ def_node_matcher :two_start_end_with_calls, <<~PATTERN
79
71
  (or
80
72
  (send $_recv [{:start_with? :end_with?} $_method] $...)
81
73
  (send _recv _method $...))
82
74
  PATTERN
83
75
 
84
- def_node_matcher :check_with_active_support_aliases, <<-PATTERN
76
+ def_node_matcher :check_with_active_support_aliases, <<~PATTERN
85
77
  (or
86
78
  (send $_recv
87
79
  [{:start_with? :starts_with? :end_with? :ends_with?} $_method]