rubocop-performance 1.6.0 → 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 (47) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +1 -1
  3. data/config/default.yml +77 -10
  4. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +39 -4
  5. data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
  6. data/lib/rubocop/cop/performance/ancestors_include.rb +48 -0
  7. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +45 -0
  8. data/lib/rubocop/cop/performance/bind_call.rb +8 -18
  9. data/lib/rubocop/cop/performance/caller.rb +3 -2
  10. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  11. data/lib/rubocop/cop/performance/casecmp.rb +12 -20
  12. data/lib/rubocop/cop/performance/chain_array_allocation.rb +4 -10
  13. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  14. data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
  15. data/lib/rubocop/cop/performance/count.rb +13 -16
  16. data/lib/rubocop/cop/performance/delete_prefix.rb +43 -28
  17. data/lib/rubocop/cop/performance/delete_suffix.rb +43 -28
  18. data/lib/rubocop/cop/performance/detect.rb +63 -31
  19. data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
  20. data/lib/rubocop/cop/performance/end_with.rb +29 -17
  21. data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
  22. data/lib/rubocop/cop/performance/flat_map.rb +20 -22
  23. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +13 -14
  24. data/lib/rubocop/cop/performance/io_readlines.rb +116 -0
  25. data/lib/rubocop/cop/performance/open_struct.rb +2 -2
  26. data/lib/rubocop/cop/performance/range_include.rb +14 -11
  27. data/lib/rubocop/cop/performance/redundant_block_call.rb +11 -6
  28. data/lib/rubocop/cop/performance/redundant_match.rb +11 -6
  29. data/lib/rubocop/cop/performance/redundant_merge.rb +18 -17
  30. data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
  31. data/lib/rubocop/cop/performance/redundant_string_chars.rb +133 -0
  32. data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
  33. data/lib/rubocop/cop/performance/reverse_each.rb +9 -5
  34. data/lib/rubocop/cop/performance/reverse_first.rb +72 -0
  35. data/lib/rubocop/cop/performance/size.rb +41 -43
  36. data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
  37. data/lib/rubocop/cop/performance/squeeze.rb +66 -0
  38. data/lib/rubocop/cop/performance/start_with.rb +29 -17
  39. data/lib/rubocop/cop/performance/string_include.rb +55 -0
  40. data/lib/rubocop/cop/performance/string_replacement.rb +23 -27
  41. data/lib/rubocop/cop/performance/sum.rb +134 -0
  42. data/lib/rubocop/cop/performance/times_map.rb +11 -18
  43. data/lib/rubocop/cop/performance/unfreeze_string.rb +2 -2
  44. data/lib/rubocop/cop/performance/uri_default_parser.rb +6 -12
  45. data/lib/rubocop/cop/performance_cops.rb +12 -0
  46. data/lib/rubocop/performance/version.rb +1 -1
  47. metadata +33 -8
@@ -5,27 +5,48 @@ module RuboCop
5
5
  module Performance
6
6
  # In Ruby 2.5, `String#delete_suffix` has been added.
7
7
  #
8
- # This cop identifies places where `gsub(/suffix\z/, '')`
8
+ # This cop identifies places where `gsub(/suffix\z/, '')` and `sub(/suffix\z/, '')`
9
9
  # can be replaced by `delete_suffix('suffix')`.
10
10
  #
11
- # The `delete_suffix('suffix')` method is faster than
12
- # `gsub(/suffix\z/, '')`.
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/, '')`.
13
16
  #
14
17
  # @example
15
18
  #
16
19
  # # bad
17
20
  # str.gsub(/suffix\z/, '')
18
21
  # str.gsub!(/suffix\z/, '')
19
- # str.gsub(/suffix$/, '')
20
- # str.gsub!(/suffix$/, '')
22
+ #
23
+ # str.sub(/suffix\z/, '')
24
+ # str.sub!(/suffix\z/, '')
21
25
  #
22
26
  # # good
23
27
  # str.delete_suffix('suffix')
24
28
  # str.delete_suffix!('suffix')
25
29
  #
26
- class DeleteSuffix < Cop
27
- extend TargetRubyVersion
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
28
47
  include RegexpMetacharacter
48
+ extend AutoCorrector
49
+ extend TargetRubyVersion
29
50
 
30
51
  minimum_target_ruby_version 2.5
31
52
 
@@ -33,37 +54,31 @@ module RuboCop
33
54
 
34
55
  PREFERRED_METHODS = {
35
56
  gsub: :delete_suffix,
36
- gsub!: :delete_suffix!
57
+ gsub!: :delete_suffix!,
58
+ sub: :delete_suffix,
59
+ sub!: :delete_suffix!
37
60
  }.freeze
38
61
 
39
- def_node_matcher :gsub_method?, <<~PATTERN
40
- (send $!nil? ${:gsub :gsub!} (regexp (str $#literal_at_end?) (regopt)) (str $_))
62
+ def_node_matcher :delete_suffix_candidate?, <<~PATTERN
63
+ (send $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_end?) (regopt)) (str $_))
41
64
  PATTERN
42
65
 
43
66
  def on_send(node)
44
- gsub_method?(node) do |_, bad_method, _, replace_string|
45
- return unless replace_string.blank?
46
-
47
- good_method = PREFERRED_METHODS[bad_method]
67
+ return unless (receiver, bad_method, regexp_str, replace_string = delete_suffix_candidate?(node))
68
+ return unless replace_string.blank?
48
69
 
49
- message = format(MSG, current: bad_method, prefer: good_method)
70
+ good_method = PREFERRED_METHODS[bad_method]
50
71
 
51
- add_offense(node, location: :selector, message: message)
52
- end
53
- end
72
+ message = format(MSG, current: bad_method, prefer: good_method)
54
73
 
55
- def autocorrect(node)
56
- gsub_method?(node) do |receiver, bad_method, regexp_str, _|
57
- lambda do |corrector|
58
- good_method = PREFERRED_METHODS[bad_method]
59
- regexp_str = drop_end_metacharacter(regexp_str)
60
- regexp_str = interpret_string_escapes(regexp_str)
61
- string_literal = to_string_literal(regexp_str)
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)
62
78
 
63
- new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
79
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
64
80
 
65
- corrector.replace(node, new_code)
66
- end
81
+ corrector.replace(node, new_code)
67
82
  end
68
83
  end
69
84
  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
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,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?
@@ -3,8 +3,11 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop identifies unnecessary use of a regex where `String#end_with?`
7
- # would suffice.
6
+ # This cop identifies unnecessary use of a regex where `String#end_with?` would suffice.
7
+ #
8
+ # This cop has `SafeMultiline` configuration option that `true` by default because
9
+ # `end$` is unsafe as it will behave incompatible with `end_with?`
10
+ # for receiver is multiline string.
8
11
  #
9
12
  # @example
10
13
  # # bad
@@ -15,6 +18,12 @@ module RuboCop
15
18
  # 'abc'.match(/bc\Z/)
16
19
  # /bc\Z/.match('abc')
17
20
  #
21
+ # # good
22
+ # 'abc'.end_with?('bc')
23
+ #
24
+ # @example SafeMultiline: true (default)
25
+ #
26
+ # # good
18
27
  # 'abc'.match?(/bc$/)
19
28
  # /bc$/.match?('abc')
20
29
  # 'abc' =~ /bc$/
@@ -22,10 +31,19 @@ module RuboCop
22
31
  # 'abc'.match(/bc$/)
23
32
  # /bc$/.match('abc')
24
33
  #
25
- # # good
26
- # 'abc'.end_with?('bc')
27
- class EndWith < Cop
34
+ # @example SafeMultiline: false
35
+ #
36
+ # # bad
37
+ # 'abc'.match?(/bc$/)
38
+ # /bc$/.match?('abc')
39
+ # 'abc' =~ /bc$/
40
+ # /bc$/ =~ 'abc'
41
+ # 'abc'.match(/bc$/)
42
+ # /bc$/.match('abc')
43
+ #
44
+ class EndWith < Base
28
45
  include RegexpMetacharacter
46
+ extend AutoCorrector
29
47
 
30
48
  MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
31
49
  'the end of the string.'
@@ -37,25 +55,19 @@ module RuboCop
37
55
  PATTERN
38
56
 
39
57
  def on_send(node)
40
- return unless redundant_regex?(node)
58
+ return unless (receiver, regex_str = redundant_regex?(node))
41
59
 
42
- add_offense(node)
43
- end
44
- alias on_match_with_lvasgn on_send
45
-
46
- def autocorrect(node)
47
- redundant_regex?(node) do |receiver, regex_str|
60
+ add_offense(node) do |corrector|
48
61
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
49
62
  regex_str = drop_end_metacharacter(regex_str)
50
63
  regex_str = interpret_string_escapes(regex_str)
51
64
 
52
- lambda do |corrector|
53
- new_source = receiver.source + '.end_with?(' +
54
- to_string_literal(regex_str) + ')'
55
- corrector.replace(node.source_range, new_source)
56
- end
65
+ new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})"
66
+
67
+ corrector.replace(node.source_range, new_source)
57
68
  end
58
69
  end
70
+ alias on_match_with_lvasgn on_send
59
71
  end
60
72
  end
61
73
  end
@@ -45,7 +45,7 @@ module RuboCop
45
45
  # waldo = { a: corge, b: grault }
46
46
  # waldo.size
47
47
  #
48
- class FixedSize < Cop
48
+ class FixedSize < Base
49
49
  MSG = 'Do not compute the size of statically sized objects.'
50
50
 
51
51
  def_node_matcher :counter, <<~MATCHER
@@ -14,8 +14,9 @@ module RuboCop
14
14
  # [1, 2, 3, 4].flat_map { |e| [e, e] }
15
15
  # [1, 2, 3, 4].map { |e| [e, e] }.flatten
16
16
  # [1, 2, 3, 4].collect { |e| [e, e] }.flatten
17
- class FlatMap < Cop
17
+ class FlatMap < Base
18
18
  include RangeHelp
19
+ extend AutoCorrector
19
20
 
20
21
  MSG = 'Use `flat_map` instead of `%<method>s...%<flatten>s`.'
21
22
  FLATTEN_MULTIPLE_LEVELS = ' Beware, `flat_map` only flattens 1 level ' \
@@ -44,25 +45,11 @@ module RuboCop
44
45
  end
45
46
  end
46
47
 
47
- def autocorrect(node)
48
- map_node, _first_method, _flatten, params = flat_map_candidate?(node)
49
- flatten_level, = *params.first
50
-
51
- return unless flatten_level
52
-
53
- range = range_between(node.loc.dot.begin_pos,
54
- node.source_range.end_pos)
55
-
56
- lambda do |corrector|
57
- corrector.remove(range)
58
- corrector.replace(map_node.loc.selector, 'flat_map')
59
- end
60
- end
61
-
62
48
  private
63
49
 
64
50
  def offense_for_levels(node, map_node, first_method, flatten)
65
51
  message = MSG + FLATTEN_MULTIPLE_LEVELS
52
+
66
53
  register_offense(node, map_node, first_method, flatten, message)
67
54
  end
68
55
 
@@ -71,13 +58,24 @@ module RuboCop
71
58
  end
72
59
 
73
60
  def register_offense(node, map_node, first_method, flatten, message)
74
- range = range_between(map_node.loc.selector.begin_pos,
75
- node.loc.expression.end_pos)
61
+ range = range_between(map_node.loc.selector.begin_pos, node.loc.expression.end_pos)
62
+ message = format(message, method: first_method, flatten: flatten)
63
+
64
+ add_offense(range, message: message) do |corrector|
65
+ autocorrect(corrector, node)
66
+ end
67
+ end
68
+
69
+ def autocorrect(corrector, node)
70
+ map_node, _first_method, _flatten, params = flat_map_candidate?(node)
71
+ flatten_level, = *params.first
72
+
73
+ return unless flatten_level
74
+
75
+ range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
76
76
 
77
- add_offense(node,
78
- location: range,
79
- message: format(message, method: first_method,
80
- flatten: flatten))
77
+ corrector.remove(range)
78
+ corrector.replace(map_node.loc.selector, 'flat_map')
81
79
  end
82
80
  end
83
81
  end