rubocop-performance 1.13.3 → 1.19.0

Sign up to get free protection for your applications and to get access to all the features.
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