rubocop-performance 1.13.3 → 1.19.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +2 -2
  4. data/config/default.yml +13 -2
  5. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +2 -2
  6. data/lib/rubocop/cop/mixin/sort_block.rb +7 -0
  7. data/lib/rubocop/cop/performance/ancestors_include.rb +1 -2
  8. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +3 -2
  9. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +2 -6
  10. data/lib/rubocop/cop/performance/bind_call.rb +1 -2
  11. data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +1 -1
  12. data/lib/rubocop/cop/performance/caller.rb +1 -2
  13. data/lib/rubocop/cop/performance/case_when_splat.rb +7 -13
  14. data/lib/rubocop/cop/performance/casecmp.rb +10 -12
  15. data/lib/rubocop/cop/performance/chain_array_allocation.rb +5 -5
  16. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +4 -6
  17. data/lib/rubocop/cop/performance/compare_with_block.rb +20 -11
  18. data/lib/rubocop/cop/performance/concurrent_monotonic_time.rb +1 -1
  19. data/lib/rubocop/cop/performance/constant_regexp.rb +6 -4
  20. data/lib/rubocop/cop/performance/count.rb +39 -3
  21. data/lib/rubocop/cop/performance/delete_prefix.rb +8 -2
  22. data/lib/rubocop/cop/performance/delete_suffix.rb +8 -2
  23. data/lib/rubocop/cop/performance/detect.rb +7 -6
  24. data/lib/rubocop/cop/performance/double_start_end_with.rb +4 -5
  25. data/lib/rubocop/cop/performance/end_with.rb +7 -6
  26. data/lib/rubocop/cop/performance/fixed_size.rb +2 -2
  27. data/lib/rubocop/cop/performance/flat_map.rb +7 -5
  28. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +17 -15
  29. data/lib/rubocop/cop/performance/io_readlines.rb +2 -2
  30. data/lib/rubocop/cop/performance/map_compact.rb +9 -4
  31. data/lib/rubocop/cop/performance/map_method_chain.rb +87 -0
  32. data/lib/rubocop/cop/performance/method_object_as_block.rb +1 -1
  33. data/lib/rubocop/cop/performance/open_struct.rb +2 -3
  34. data/lib/rubocop/cop/performance/range_include.rb +2 -2
  35. data/lib/rubocop/cop/performance/redundant_block_call.rb +2 -2
  36. data/lib/rubocop/cop/performance/redundant_equality_comparison_block.rb +38 -3
  37. data/lib/rubocop/cop/performance/redundant_match.rb +10 -9
  38. data/lib/rubocop/cop/performance/redundant_merge.rb +9 -16
  39. data/lib/rubocop/cop/performance/redundant_sort_block.rb +17 -10
  40. data/lib/rubocop/cop/performance/redundant_split_regexp_argument.rb +3 -2
  41. data/lib/rubocop/cop/performance/redundant_string_chars.rb +10 -6
  42. data/lib/rubocop/cop/performance/regexp_match.rb +23 -24
  43. data/lib/rubocop/cop/performance/reverse_each.rb +3 -3
  44. data/lib/rubocop/cop/performance/reverse_first.rb +4 -3
  45. data/lib/rubocop/cop/performance/select_map.rb +2 -1
  46. data/lib/rubocop/cop/performance/size.rb +1 -2
  47. data/lib/rubocop/cop/performance/sort_reverse.rb +19 -10
  48. data/lib/rubocop/cop/performance/squeeze.rb +8 -8
  49. data/lib/rubocop/cop/performance/start_with.rb +7 -6
  50. data/lib/rubocop/cop/performance/string_identifier_argument.rb +16 -11
  51. data/lib/rubocop/cop/performance/string_include.rb +23 -17
  52. data/lib/rubocop/cop/performance/string_replacement.rb +7 -10
  53. data/lib/rubocop/cop/performance/sum.rb +7 -4
  54. data/lib/rubocop/cop/performance/times_map.rb +6 -7
  55. data/lib/rubocop/cop/performance/uri_default_parser.rb +4 -6
  56. data/lib/rubocop/cop/performance_cops.rb +1 -0
  57. data/lib/rubocop/performance/version.rb +1 -1
  58. metadata +6 -5
@@ -3,14 +3,14 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop is used to identify usages of `first`, `last`, `[0]` or `[-1]`
6
+ # Identifies usages of `first`, `last`, `[0]` or `[-1]`
7
7
  # chained to `select`, `find_all` or `filter` and change them to use
8
8
  # `detect` instead.
9
9
  #
10
10
  # @safety
11
- # This cop is unsafe because is has known compatibility issues with `ActiveRecord` and other
12
- # frameworks. `ActiveRecord` does not implement a `detect` method and `find` has its own
13
- # meaning. Correcting `ActiveRecord` methods with this cop should be considered unsafe.
11
+ # This cop is unsafe because it assumes that the receiver is an
12
+ # `Array` or equivalent, but can't reliably detect it. For example,
13
+ # if the receiver is a `Hash`, it may report a false positive.
14
14
  #
15
15
  # @example
16
16
  # # bad
@@ -40,9 +40,9 @@ module RuboCop
40
40
 
41
41
  def_node_matcher :detect_candidate?, <<~PATTERN
42
42
  {
43
- (send $(block (send _ %CANDIDATE_METHODS) ...) ${:first :last} $...)
43
+ (send $(block (call _ %CANDIDATE_METHODS) ...) ${:first :last} $...)
44
44
  (send $(block (send _ %CANDIDATE_METHODS) ...) $:[] (int ${0 -1}))
45
- (send $(send _ %CANDIDATE_METHODS ...) ${:first :last} $...)
45
+ (send $(call _ %CANDIDATE_METHODS ...) ${:first :last} $...)
46
46
  (send $(send _ %CANDIDATE_METHODS ...) $:[] (int ${0 -1}))
47
47
  }
48
48
  PATTERN
@@ -63,6 +63,7 @@ module RuboCop
63
63
  register_offense(node, receiver, second_method, index)
64
64
  end
65
65
  end
66
+ alias on_csend on_send
66
67
 
67
68
  private
68
69
 
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop checks for double `#start_with?` or `#end_with?` calls
6
+ # Checks for double `#start_with?` or `#end_with?` calls
7
7
  # separated by `||`. In some cases such calls can be replaced
8
8
  # with an single `#start_with?`/`#end_with?` call.
9
9
  #
@@ -41,8 +41,7 @@ module RuboCop
41
41
  class DoubleStartEndWith < Base
42
42
  extend AutoCorrector
43
43
 
44
- MSG = 'Use `%<receiver>s.%<method>s(%<combined_args>s)` ' \
45
- 'instead of `%<original_code>s`.'
44
+ MSG = 'Use `%<receiver>s.%<method>s(%<combined_args>s)` instead of `%<original_code>s`.'
46
45
 
47
46
  def on_or(node)
48
47
  receiver, method, first_call_args, second_call_args = process_source(node)
@@ -59,8 +58,8 @@ module RuboCop
59
58
  private
60
59
 
61
60
  def autocorrect(corrector, first_call_args, second_call_args, combined_args)
62
- first_argument = first_call_args.first.loc.expression
63
- last_argument = second_call_args.last.loc.expression
61
+ first_argument = first_call_args.first.source_range
62
+ last_argument = second_call_args.last.source_range
64
63
  range = first_argument.join(last_argument)
65
64
 
66
65
  corrector.replace(range, combined_args)
@@ -3,7 +3,7 @@
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?` would suffice.
6
+ # Identifies unnecessary use of a regex where `String#end_with?` would suffice.
7
7
  #
8
8
  # This cop has `SafeMultiline` configuration option that `true` by default because
9
9
  # `end$` is unsafe as it will behave incompatible with `end_with?`
@@ -50,12 +50,11 @@ module RuboCop
50
50
  include RegexpMetacharacter
51
51
  extend AutoCorrector
52
52
 
53
- MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
54
- 'the end of the string.'
53
+ MSG = 'Use `String#end_with?` instead of a regex match anchored to the end of the string.'
55
54
  RESTRICT_ON_SEND = %i[match =~ match?].freeze
56
55
 
57
56
  def_node_matcher :redundant_regex?, <<~PATTERN
58
- {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
57
+ {(call $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
59
58
  (send (regexp (str $#literal_at_end?) (regopt)) {:match :match?} $_)
60
59
  (match-with-lvasgn (regexp (str $#literal_at_end?) (regopt)) $_)}
61
60
  PATTERN
@@ -67,12 +66,14 @@ module RuboCop
67
66
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
68
67
  regex_str = drop_end_metacharacter(regex_str)
69
68
  regex_str = interpret_string_escapes(regex_str)
69
+ dot = node.loc.dot ? node.loc.dot.source : '.'
70
70
 
71
- new_source = "#{receiver.source}.end_with?(#{to_string_literal(regex_str)})"
71
+ new_source = "#{receiver.source}#{dot}end_with?(#{to_string_literal(regex_str)})"
72
72
 
73
- corrector.replace(node.source_range, new_source)
73
+ corrector.replace(node, new_source)
74
74
  end
75
75
  end
76
+ alias on_csend on_send
76
77
  alias on_match_with_lvasgn on_send
77
78
  end
78
79
  end
@@ -78,13 +78,13 @@ module RuboCop
78
78
  end
79
79
 
80
80
  def contains_splat?(node)
81
- return unless node.array_type?
81
+ return false unless node.array_type?
82
82
 
83
83
  node.each_child_node(:splat).any?
84
84
  end
85
85
 
86
86
  def contains_double_splat?(node)
87
- return unless node.hash_type?
87
+ return false unless node.hash_type?
88
88
 
89
89
  node.each_child_node(:kwsplat).any?
90
90
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop is used to identify usages of `map { ... }.flatten` and
6
+ # Identifies usages of `map { ... }.flatten` and
7
7
  # change them to use `flat_map { ... }` instead.
8
8
  #
9
9
  # @example
@@ -28,7 +28,7 @@ module RuboCop
28
28
  def_node_matcher :flat_map_candidate?, <<~PATTERN
29
29
  (send
30
30
  {
31
- (block $(send _ ${:collect :map}) ...)
31
+ $(block (send _ ${:collect :map}) ...)
32
32
  $(send _ ${:collect :map} (block_pass _))
33
33
  }
34
34
  ${:flatten :flatten!}
@@ -60,7 +60,8 @@ module RuboCop
60
60
  end
61
61
 
62
62
  def register_offense(node, map_node, first_method, flatten, message)
63
- range = range_between(map_node.loc.selector.begin_pos, node.loc.expression.end_pos)
63
+ map_send_node = map_node.block_type? ? map_node.send_node : map_node
64
+ range = range_between(map_send_node.loc.selector.begin_pos, node.source_range.end_pos)
64
65
  message = format(message, method: first_method, flatten: flatten)
65
66
 
66
67
  add_offense(range, message: message) do |corrector|
@@ -74,10 +75,11 @@ module RuboCop
74
75
 
75
76
  return unless flatten_level
76
77
 
77
- range = range_between(node.loc.dot.begin_pos, node.source_range.end_pos)
78
+ map_send_node = map_node.block_type? ? map_node.send_node : map_node
79
+ range = range_between(map_node.source_range.end_pos, node.source_range.end_pos)
78
80
 
79
81
  corrector.remove(range)
80
- corrector.replace(map_node.loc.selector, 'flat_map')
82
+ corrector.replace(map_send_node.loc.selector, 'flat_map')
81
83
  end
82
84
  end
83
85
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop checks for inefficient searching of keys and values within
6
+ # Checks for inefficient searching of keys and values within
7
7
  # hashes.
8
8
  #
9
9
  # `Hash#keys.include?` is less efficient than `Hash#key?` because
@@ -45,7 +45,7 @@ module RuboCop
45
45
  RESTRICT_ON_SEND = %i[include?].freeze
46
46
 
47
47
  def_node_matcher :inefficient_include?, <<~PATTERN
48
- (send (send $_ {:keys :values}) :include? _)
48
+ (send (call $_ {:keys :values}) :include? _)
49
49
  PATTERN
50
50
 
51
51
  def on_send(node)
@@ -56,23 +56,23 @@ module RuboCop
56
56
  add_offense(node, message: message) do |corrector|
57
57
  # Replace `keys.include?` or `values.include?` with the appropriate
58
58
  # `key?`/`value?` method.
59
- corrector.replace(
60
- node.loc.expression,
61
- "#{autocorrect_hash_expression(node)}."\
62
- "#{autocorrect_method(node)}(#{autocorrect_argument(node)})"
63
- )
59
+ corrector.replace(node, replacement(node))
64
60
  end
65
61
  end
66
62
  end
63
+ alias on_csend on_send
67
64
 
68
65
  private
69
66
 
70
67
  def message(node)
71
- "Use `##{autocorrect_method(node)}` instead of "\
72
- "`##{current_method(node)}.include?`."
68
+ "Use `##{correct_method(node)}` instead of `##{current_method(node)}.include?`."
73
69
  end
74
70
 
75
- def autocorrect_method(node)
71
+ def replacement(node)
72
+ "#{correct_hash_expression(node)}#{correct_dot(node)}#{correct_method(node)}(#{correct_argument(node)})"
73
+ end
74
+
75
+ def correct_method(node)
76
76
  case current_method(node)
77
77
  when :keys then use_long_method ? 'has_key?' : 'key?'
78
78
  when :values then use_long_method ? 'has_value?' : 'value?'
@@ -85,18 +85,20 @@ module RuboCop
85
85
 
86
86
  def use_long_method
87
87
  preferred_config = config.for_all_cops['Style/PreferredHashMethods']
88
- preferred_config &&
89
- preferred_config['EnforcedStyle'] == 'long' &&
90
- preferred_config['Enabled']
88
+ preferred_config && preferred_config['EnforcedStyle'] == 'long' && preferred_config['Enabled']
91
89
  end
92
90
 
93
- def autocorrect_argument(node)
91
+ def correct_argument(node)
94
92
  node.arguments.first.source
95
93
  end
96
94
 
97
- def autocorrect_hash_expression(node)
95
+ def correct_hash_expression(node)
98
96
  node.receiver.receiver.source
99
97
  end
98
+
99
+ def correct_dot(node)
100
+ node.receiver.loc.dot.source
101
+ end
100
102
  end
101
103
  end
102
104
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop identifies places where inefficient `readlines` method
6
+ # Identifies places where inefficient `readlines` method
7
7
  # can be replaced by `each_line` to avoid fully loading file content into memory.
8
8
  #
9
9
  # @example
@@ -95,7 +95,7 @@ module RuboCop
95
95
  begin_pos = readlines_call.loc.selector.begin_pos
96
96
 
97
97
  end_pos = if enumerable_call.method?(:each)
98
- enumerable_call.loc.expression.end_pos
98
+ enumerable_call.source_range.end_pos
99
99
  else
100
100
  enumerable_call.loc.dot.begin_pos
101
101
  end
@@ -40,12 +40,12 @@ module RuboCop
40
40
  def_node_matcher :map_compact, <<~PATTERN
41
41
  {
42
42
  (send
43
- $(send _ {:map :collect}
43
+ $(call _ {:map :collect}
44
44
  (block_pass
45
45
  (sym _))) _)
46
46
  (send
47
47
  (block
48
- $(send _ {:map :collect})
48
+ $(call _ {:map :collect})
49
49
  (args ...) _) _)
50
50
  }
51
51
  PATTERN
@@ -61,13 +61,14 @@ module RuboCop
61
61
  remove_compact_method(corrector, map_node, node, node.parent)
62
62
  end
63
63
  end
64
+ alias on_csend on_send
64
65
 
65
66
  private
66
67
 
67
68
  def remove_compact_method(corrector, map_node, compact_node, chained_method)
68
69
  compact_method_range = compact_node.loc.selector
69
70
 
70
- if compact_node.multiline? && chained_method&.loc.respond_to?(:selector) && chained_method.dot? &&
71
+ if compact_node.multiline? && chained_method&.loc.respond_to?(:selector) && use_dot?(chained_method) &&
71
72
  !map_method_and_compact_method_on_same_line?(map_node, compact_node) &&
72
73
  !invoke_method_after_map_compact_on_same_line?(compact_node, chained_method)
73
74
  compact_method_range = compact_method_with_final_newline_range(compact_method_range)
@@ -78,12 +79,16 @@ module RuboCop
78
79
  corrector.remove(compact_method_range)
79
80
  end
80
81
 
82
+ def use_dot?(node)
83
+ node.respond_to?(:dot?) && node.dot?
84
+ end
85
+
81
86
  def map_method_and_compact_method_on_same_line?(map_node, compact_node)
82
87
  compact_node.loc.selector.line == map_node.loc.selector.line
83
88
  end
84
89
 
85
90
  def invoke_method_after_map_compact_on_same_line?(compact_node, chained_method)
86
- compact_node.loc.selector.line == chained_method.loc.selector.line
91
+ compact_node.loc.selector.line == chained_method.loc.last_line
87
92
  end
88
93
 
89
94
  def compact_method_with_final_newline_range(compact_method_range)
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # Checks if the map method is used in a chain.
7
+ #
8
+ # Autocorrection is not supported because an appropriate block variable name cannot be determined automatically.
9
+ #
10
+ # @safety
11
+ # This cop is unsafe because false positives occur if the number of times the first method is executed
12
+ # affects the return value of subsequent methods.
13
+ #
14
+ # [source,ruby]
15
+ # ----
16
+ # class X
17
+ # def initialize
18
+ # @@num = 0
19
+ # end
20
+ #
21
+ # def foo
22
+ # @@num += 1
23
+ # self
24
+ # end
25
+ #
26
+ # def bar
27
+ # @@num * 2
28
+ # end
29
+ # end
30
+ #
31
+ # [X.new, X.new].map(&:foo).map(&:bar) # => [4, 4]
32
+ # [X.new, X.new].map { |x| x.foo.bar } # => [2, 4]
33
+ # ----
34
+ #
35
+ # @example
36
+ #
37
+ # # bad
38
+ # array.map(&:foo).map(&:bar)
39
+ #
40
+ # # good
41
+ # array.map { |item| item.foo.bar }
42
+ #
43
+ class MapMethodChain < Base
44
+ include IgnoredNode
45
+
46
+ MSG = 'Use `%<method_name>s { |x| x.%<map_args>s }` instead of `%<method_name>s` method chain.'
47
+ RESTRICT_ON_SEND = %i[map collect].freeze
48
+
49
+ def_node_matcher :block_pass_with_symbol_arg?, <<~PATTERN
50
+ (:block_pass (:sym $_))
51
+ PATTERN
52
+
53
+ def on_send(node)
54
+ return if part_of_ignored_node?(node)
55
+ return unless (map_arg = block_pass_with_symbol_arg?(node.first_argument))
56
+
57
+ map_args = [map_arg]
58
+
59
+ return unless (begin_of_chained_map_method = find_begin_of_chained_map_method(node, map_args))
60
+
61
+ range = begin_of_chained_map_method.loc.selector.begin.join(node.source_range.end)
62
+ message = format(MSG, method_name: begin_of_chained_map_method.method_name, map_args: map_args.join('.'))
63
+
64
+ add_offense(range, message: message)
65
+
66
+ ignore_node(node)
67
+ end
68
+
69
+ private
70
+
71
+ def find_begin_of_chained_map_method(node, map_args)
72
+ return unless (chained_map_method = node.receiver)
73
+ return if !chained_map_method.call_type? || !RESTRICT_ON_SEND.include?(chained_map_method.method_name)
74
+ return unless (map_arg = block_pass_with_symbol_arg?(chained_map_method.first_argument))
75
+
76
+ map_args.unshift(map_arg)
77
+
78
+ receiver = chained_map_method.receiver
79
+
80
+ return chained_map_method unless receiver.call_type? && block_pass_with_symbol_arg?(receiver.first_argument)
81
+
82
+ find_begin_of_chained_map_method(chained_map_method, map_args)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop identifies places where methods are converted to blocks, with the
6
+ # Identifies places where methods are converted to blocks, with the
7
7
  # use of `&method`, and passed as arguments to method calls.
8
8
  # It is faster to replace those with explicit blocks, calling those methods inside.
9
9
  #
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop checks for `OpenStruct.new` calls.
6
+ # Checks for `OpenStruct.new` calls.
7
7
  # Instantiation of an `OpenStruct` invalidates
8
8
  # Ruby global method cache as it causes dynamic method
9
9
  # definition during program runtime.
@@ -32,8 +32,7 @@ module RuboCop
32
32
  # end
33
33
  #
34
34
  class OpenStruct < Base
35
- MSG = 'Consider using `Struct` over `OpenStruct` ' \
36
- 'to optimize the performance.'
35
+ MSG = 'Consider using `Struct` over `OpenStruct` to optimize the performance.'
37
36
  RESTRICT_ON_SEND = %i[new].freeze
38
37
 
39
38
  def_node_matcher :open_struct, <<~PATTERN
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop identifies uses of `Range#include?` and `Range#member?`, which iterates over each
6
+ # Identifies uses of `Range#include?` and `Range#member?`, which iterates over each
7
7
  # item in a `Range` to see if a specified item is there. In contrast,
8
8
  # `Range#cover?` simply compares the target item with the beginning and
9
9
  # end points of the `Range`. In a great majority of cases, this is what
@@ -11,7 +11,7 @@ module RuboCop
11
11
  #
12
12
  # @safety
13
13
  # This cop is unsafe because `Range#include?` (or `Range#member?`) and `Range#cover?`
14
- # are not equivalent behaviour.
14
+ # are not equivalent behavior.
15
15
  #
16
16
  # @example
17
17
  # # bad
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop identifies the use of a `&block` parameter and `block.call`
6
+ # Identifies the use of a `&block` parameter and `block.call`
7
7
  # where `yield` would do just as well.
8
8
  #
9
9
  # @example
@@ -75,7 +75,7 @@ module RuboCop
75
75
 
76
76
  new_source << CLOSE_PAREN if parentheses?(node) && !args.empty?
77
77
 
78
- corrector.replace(node.source_range, new_source)
78
+ corrector.replace(node, new_source)
79
79
  end
80
80
 
81
81
  def calls_to_report(argname, body)
@@ -3,13 +3,23 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop checks for uses `Enumerable#all?`, `Enumerable#any?`, `Enumerable#one?`,
6
+ # Checks for uses `Enumerable#all?`, `Enumerable#any?`, `Enumerable#one?`,
7
7
  # and `Enumerable#none?` are compared with `===` or similar methods in block.
8
8
  #
9
9
  # By default, `Object#===` behaves the same as `Object#==`, but this
10
10
  # behavior is appropriately overridden in subclass. For example,
11
11
  # `Range#===` returns `true` when argument is within the range.
12
12
  #
13
+ # This cop has `AllowRegexpMatch` option and it is true by default because
14
+ # `regexp.match?('string')` often used in block changes to the opposite result:
15
+ #
16
+ # [source,ruby]
17
+ # ----
18
+ # [/pattern/].all? { |regexp| regexp.match?('pattern') } # => true
19
+ # [/pattern/].all? { |regexp| regexp =~ 'pattern' } # => true
20
+ # [/pattern/].all?('pattern') # => false
21
+ # ----
22
+ #
13
23
  # @safety
14
24
  # This cop is unsafe because `===` and `==` do not always behave the same.
15
25
  #
@@ -22,14 +32,31 @@ module RuboCop
22
32
  #
23
33
  # # good
24
34
  # items.all?(pattern)
35
+ # items.all?(Klass)
36
+ #
37
+ # @example AllowRegexpMatch: true (default)
38
+ #
39
+ # # good
40
+ # items.all? { |item| item =~ pattern }
41
+ # items.all? { |item| item.match?(pattern) }
42
+ #
43
+ # @example AllowRegexpMatch: false
44
+ #
45
+ # # bad
46
+ # items.all? { |item| item =~ pattern }
47
+ # items.all? { |item| item.match?(pattern) }
25
48
  #
26
49
  class RedundantEqualityComparisonBlock < Base
27
50
  extend AutoCorrector
51
+ extend TargetRubyVersion
52
+
53
+ minimum_target_ruby_version 2.5
28
54
 
29
55
  MSG = 'Use `%<prefer>s` instead of block.'
30
56
 
31
57
  TARGET_METHODS = %i[all? any? one? none?].freeze
32
58
  COMPARISON_METHODS = %i[== === is_a? kind_of?].freeze
59
+ REGEXP_METHODS = %i[=~ match?].freeze
33
60
  IS_A_METHODS = %i[is_a? kind_of?].freeze
34
61
 
35
62
  def on_block(node)
@@ -57,7 +84,11 @@ module RuboCop
57
84
  end
58
85
 
59
86
  def use_equality_comparison_block?(block_body)
60
- block_body.send_type? && COMPARISON_METHODS.include?(block_body.method_name)
87
+ return false unless block_body.send_type?
88
+
89
+ method_name = block_body.method_name
90
+
91
+ COMPARISON_METHODS.include?(method_name) || (!allow_regexp_match? && REGEXP_METHODS.include?(method_name))
61
92
  end
62
93
 
63
94
  def same_block_argument_and_is_a_argument?(block_body, block_argument)
@@ -66,7 +97,7 @@ module RuboCop
66
97
  elsif IS_A_METHODS.include?(block_body.method_name)
67
98
  block_argument.source == block_body.first_argument.source
68
99
  else
69
- false
100
+ block_body.receiver.source == block_body.first_argument.receiver&.source
70
101
  end
71
102
  end
72
103
 
@@ -96,6 +127,10 @@ module RuboCop
96
127
  def offense_range(node)
97
128
  node.send_node.loc.selector.join(node.source_range.end)
98
129
  end
130
+
131
+ def allow_regexp_match?
132
+ cop_config.fetch('AllowRegexpMatch', true)
133
+ end
99
134
  end
100
135
  end
101
136
  end
@@ -3,7 +3,7 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop identifies the use of `Regexp#match` or `String#match`, which
6
+ # Identifies the use of `Regexp#match` or `String#match`, which
7
7
  # returns `#<MatchData>`/`nil`. The return value of `=~` is an integral
8
8
  # index/`nil` and is more performant.
9
9
  #
@@ -20,8 +20,7 @@ module RuboCop
20
20
  class RedundantMatch < Base
21
21
  extend AutoCorrector
22
22
 
23
- MSG = 'Use `=~` in places where the `MatchData` returned by ' \
24
- '`#match` will not be used.'
23
+ MSG = 'Use `=~` in places where the `MatchData` returned by `#match` will not be used.'
25
24
  RESTRICT_ON_SEND = %i[match].freeze
26
25
 
27
26
  # 'match' is a fairly generic name, so we don't flag it unless we see
@@ -41,20 +40,22 @@ module RuboCop
41
40
  !(node.parent && node.parent.block_type?)
42
41
 
43
42
  add_offense(node) do |corrector|
44
- autocorrect(corrector, node)
43
+ autocorrect(corrector, node) if autocorrectable?(node)
45
44
  end
46
45
  end
47
46
 
48
47
  private
49
48
 
50
49
  def autocorrect(corrector, node)
51
- # Regexp#match can take a second argument, but this cop doesn't
52
- # register an offense in that case
53
- return unless node.first_argument.regexp_type?
54
-
55
50
  new_source = "#{node.receiver.source} =~ #{node.first_argument.source}"
56
51
 
57
- corrector.replace(node.source_range, new_source)
52
+ corrector.replace(node, new_source)
53
+ end
54
+
55
+ def autocorrectable?(node)
56
+ # Regexp#match can take a second argument, but this cop doesn't
57
+ # register an offense in that case
58
+ node.receiver.regexp_type? || node.first_argument.regexp_type?
58
59
  end
59
60
  end
60
61
  end