rubocop-performance 1.4.1 → 1.6.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +5 -1
  4. data/config/default.yml +29 -10
  5. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +76 -0
  6. data/lib/rubocop/cop/performance/bind_call.rb +87 -0
  7. data/lib/rubocop/cop/performance/caller.rb +3 -3
  8. data/lib/rubocop/cop/performance/casecmp.rb +5 -3
  9. data/lib/rubocop/cop/performance/chain_array_allocation.rb +1 -1
  10. data/lib/rubocop/cop/performance/compare_with_block.rb +2 -2
  11. data/lib/rubocop/cop/performance/count.rb +3 -6
  12. data/lib/rubocop/cop/performance/delete_prefix.rb +96 -0
  13. data/lib/rubocop/cop/performance/delete_suffix.rb +96 -0
  14. data/lib/rubocop/cop/performance/detect.rb +1 -5
  15. data/lib/rubocop/cop/performance/double_start_end_with.rb +2 -2
  16. data/lib/rubocop/cop/performance/end_with.rb +36 -13
  17. data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
  18. data/lib/rubocop/cop/performance/flat_map.rb +9 -2
  19. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +1 -1
  20. data/lib/rubocop/cop/performance/open_struct.rb +1 -1
  21. data/lib/rubocop/cop/performance/range_include.rb +1 -1
  22. data/lib/rubocop/cop/performance/redundant_block_call.rb +3 -3
  23. data/lib/rubocop/cop/performance/redundant_match.rb +2 -2
  24. data/lib/rubocop/cop/performance/redundant_merge.rb +22 -9
  25. data/lib/rubocop/cop/performance/regexp_match.rb +13 -13
  26. data/lib/rubocop/cop/performance/reverse_each.rb +3 -2
  27. data/lib/rubocop/cop/performance/size.rb +2 -2
  28. data/lib/rubocop/cop/performance/start_with.rb +36 -16
  29. data/lib/rubocop/cop/performance/string_replacement.rb +4 -11
  30. data/lib/rubocop/cop/performance/times_map.rb +1 -1
  31. data/lib/rubocop/cop/performance/unfreeze_string.rb +3 -7
  32. data/lib/rubocop/cop/performance/uri_default_parser.rb +1 -1
  33. data/lib/rubocop/cop/performance_cops.rb +5 -0
  34. data/lib/rubocop/performance/inject.rb +1 -1
  35. data/lib/rubocop/performance/version.rb +1 -1
  36. metadata +13 -9
@@ -0,0 +1,96 @@
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 < Cop
47
+ extend TargetRubyVersion
48
+ include RegexpMetacharacter
49
+
50
+ minimum_target_ruby_version 2.5
51
+
52
+ MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
53
+
54
+ PREFERRED_METHODS = {
55
+ gsub: :delete_suffix,
56
+ gsub!: :delete_suffix!,
57
+ sub: :delete_suffix,
58
+ sub!: :delete_suffix!
59
+ }.freeze
60
+
61
+ def_node_matcher :delete_suffix_candidate?, <<~PATTERN
62
+ (send $!nil? ${:gsub :gsub! :sub :sub!} (regexp (str $#literal_at_end?) (regopt)) (str $_))
63
+ PATTERN
64
+
65
+ 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]
70
+
71
+ message = format(MSG, current: bad_method, prefer: good_method)
72
+
73
+ add_offense(node, location: :selector, message: message)
74
+ end
75
+ end
76
+
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)
84
+
85
+ new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
86
+
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
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -23,14 +23,12 @@ module RuboCop
23
23
  # own meaning. Correcting ActiveRecord methods with this cop should be
24
24
  # considered unsafe.
25
25
  class Detect < Cop
26
- include SafeMode
27
-
28
26
  MSG = 'Use `%<prefer>s` instead of ' \
29
27
  '`%<first_method>s.%<second_method>s`.'
30
28
  REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
31
29
  '`%<first_method>s.%<second_method>s`.'
32
30
 
33
- def_node_matcher :detect_candidate?, <<-PATTERN
31
+ def_node_matcher :detect_candidate?, <<~PATTERN
34
32
  {
35
33
  (send $(block (send _ {:select :find_all}) ...) ${:first :last} $...)
36
34
  (send $(send _ {:select :find_all} ...) ${:first :last} $...)
@@ -38,8 +36,6 @@ module RuboCop
38
36
  PATTERN
39
37
 
40
38
  def on_send(node)
41
- return if rails_safe_mode?
42
-
43
39
  detect_candidate?(node) do |receiver, second_method, args|
44
40
  return unless args.empty?
45
41
  return unless receiver
@@ -75,13 +75,13 @@ module RuboCop
75
75
  cop_config['IncludeActiveSupportAliases']
76
76
  end
77
77
 
78
- def_node_matcher :two_start_end_with_calls, <<-PATTERN
78
+ def_node_matcher :two_start_end_with_calls, <<~PATTERN
79
79
  (or
80
80
  (send $_recv [{:start_with? :end_with?} $_method] $...)
81
81
  (send _recv _method $...))
82
82
  PATTERN
83
83
 
84
- def_node_matcher :check_with_active_support_aliases, <<-PATTERN
84
+ def_node_matcher :check_with_active_support_aliases, <<~PATTERN
85
85
  (or
86
86
  (send $_recv
87
87
  [{:start_with? :starts_with? :end_with? :ends_with?} $_method]
@@ -3,44 +3,67 @@
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
11
14
  # 'abc'.match?(/bc\Z/)
15
+ # /bc\Z/.match?('abc')
12
16
  # 'abc' =~ /bc\Z/
17
+ # /bc\Z/ =~ 'abc'
13
18
  # 'abc'.match(/bc\Z/)
19
+ # /bc\Z/.match('abc')
14
20
  #
15
21
  # # good
16
22
  # 'abc'.end_with?('bc')
23
+ #
24
+ # @example SafeMultiline: true (default)
25
+ #
26
+ # # good
27
+ # 'abc'.match?(/bc$/)
28
+ # /bc$/.match?('abc')
29
+ # 'abc' =~ /bc$/
30
+ # /bc$/ =~ 'abc'
31
+ # 'abc'.match(/bc$/)
32
+ # /bc$/.match('abc')
33
+ #
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
+ #
17
44
  class EndWith < Cop
45
+ include RegexpMetacharacter
46
+
18
47
  MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
19
48
  'the end of the string.'
20
- SINGLE_QUOTE = "'"
21
49
 
22
- def_node_matcher :redundant_regex?, <<-PATTERN
50
+ def_node_matcher :redundant_regex?, <<~PATTERN
23
51
  {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
24
- (send (regexp (str $#literal_at_end?) (regopt)) {:match :=~} $_)}
52
+ (send (regexp (str $#literal_at_end?) (regopt)) {:match :match?} $_)
53
+ (match-with-lvasgn (regexp (str $#literal_at_end?) (regopt)) $_)}
25
54
  PATTERN
26
55
 
27
- def literal_at_end?(regex_str)
28
- # is this regexp 'literal' in the sense of only matching literal
29
- # chars, rather than using metachars like . and * and so on?
30
- # also, is it anchored at the end of the string?
31
- regex_str =~ /\A(?:#{LITERAL_REGEX})+\\z\z/
32
- end
33
-
34
56
  def on_send(node)
35
57
  return unless redundant_regex?(node)
36
58
 
37
59
  add_offense(node)
38
60
  end
61
+ alias on_match_with_lvasgn on_send
39
62
 
40
63
  def autocorrect(node)
41
64
  redundant_regex?(node) do |receiver, regex_str|
42
65
  receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
43
- regex_str = regex_str[0..-3] # drop \Z anchor
66
+ regex_str = drop_end_metacharacter(regex_str)
44
67
  regex_str = interpret_string_escapes(regex_str)
45
68
 
46
69
  lambda do |corrector|
@@ -48,7 +48,7 @@ module RuboCop
48
48
  class FixedSize < Cop
49
49
  MSG = 'Do not compute the size of statically sized objects.'
50
50
 
51
- def_node_matcher :counter, <<-MATCHER
51
+ def_node_matcher :counter, <<~MATCHER
52
52
  (send ${array hash str sym} {:count :length :size} $...)
53
53
  MATCHER
54
54
 
@@ -22,8 +22,15 @@ module RuboCop
22
22
  'and `flatten` can be used to flatten ' \
23
23
  'multiple levels.'
24
24
 
25
- def_node_matcher :flat_map_candidate?, <<-PATTERN
26
- (send (block $(send _ ${:collect :map}) ...) ${:flatten :flatten!} $...)
25
+ def_node_matcher :flat_map_candidate?, <<~PATTERN
26
+ (send
27
+ {
28
+ (block $(send _ ${:collect :map}) ...)
29
+ $(send _ ${:collect :map} (block_pass _))
30
+ }
31
+ ${:flatten :flatten!}
32
+ $...
33
+ )
27
34
  PATTERN
28
35
 
29
36
  def on_send(node)
@@ -37,7 +37,7 @@ module RuboCop
37
37
  # h = { a: 1, b: 2 }; h.value?(nil)
38
38
  #
39
39
  class InefficientHashSearch < Cop
40
- def_node_matcher :inefficient_include?, <<-PATTERN
40
+ def_node_matcher :inefficient_include?, <<~PATTERN
41
41
  (send (send $_ {:keys :values}) :include? _)
42
42
  PATTERN
43
43
 
@@ -31,7 +31,7 @@ module RuboCop
31
31
  MSG = 'Consider using `Struct` over `OpenStruct` ' \
32
32
  'to optimize the performance.'
33
33
 
34
- def_node_matcher :open_struct, <<-PATTERN
34
+ def_node_matcher :open_struct, <<~PATTERN
35
35
  (send (const {nil? cbase} :OpenStruct) :new ...)
36
36
  PATTERN
37
37
 
@@ -31,7 +31,7 @@ module RuboCop
31
31
  # Right now, we only detect direct calls on a Range literal
32
32
  # (We don't even catch it if the Range is in double parens)
33
33
 
34
- def_node_matcher :range_include, <<-PATTERN
34
+ def_node_matcher :range_include, <<~PATTERN
35
35
  (send {irange erange (begin {irange erange})} :include? ...)
36
36
  PATTERN
37
37
 
@@ -29,16 +29,16 @@ module RuboCop
29
29
  CLOSE_PAREN = ')'
30
30
  SPACE = ' '
31
31
 
32
- def_node_matcher :blockarg_def, <<-PATTERN
32
+ def_node_matcher :blockarg_def, <<~PATTERN
33
33
  {(def _ (args ... (blockarg $_)) $_)
34
34
  (defs _ _ (args ... (blockarg $_)) $_)}
35
35
  PATTERN
36
36
 
37
- def_node_search :blockarg_calls, <<-PATTERN
37
+ def_node_search :blockarg_calls, <<~PATTERN
38
38
  (send (lvar %1) :call ...)
39
39
  PATTERN
40
40
 
41
- def_node_search :blockarg_assigned?, <<-PATTERN
41
+ def_node_search :blockarg_assigned?, <<~PATTERN
42
42
  (lvasgn %1 ...)
43
43
  PATTERN
44
44
 
@@ -23,12 +23,12 @@ module RuboCop
23
23
 
24
24
  # 'match' is a fairly generic name, so we don't flag it unless we see
25
25
  # a string or regexp literal on one side or the other
26
- def_node_matcher :match_call?, <<-PATTERN
26
+ def_node_matcher :match_call?, <<~PATTERN
27
27
  {(send {str regexp} :match _)
28
28
  (send !nil? :match {str regexp})}
29
29
  PATTERN
30
30
 
31
- def_node_matcher :only_truthiness_matters?, <<-PATTERN
31
+ def_node_matcher :only_truthiness_matters?, <<~PATTERN
32
32
  ^({if while until case while_post until_post} equal?(%0) ...)
33
33
  PATTERN
34
34
 
@@ -5,11 +5,25 @@ module RuboCop
5
5
  module Performance
6
6
  # This cop identifies places where `Hash#merge!` can be replaced by
7
7
  # `Hash#[]=`.
8
+ # You can set the maximum number of key-value pairs to consider
9
+ # an offense with `MaxKeyValuePairs`.
8
10
  #
9
11
  # @example
12
+ # # bad
10
13
  # hash.merge!(a: 1)
11
14
  # hash.merge!({'key' => 'value'})
15
+ #
16
+ # # good
17
+ # hash[:a] = 1
18
+ # hash['key'] = 'value'
19
+ #
20
+ # @example MaxKeyValuePairs: 2 (default)
21
+ # # bad
12
22
  # hash.merge!(a: 1, b: 2)
23
+ #
24
+ # # good
25
+ # hash[:a] = 1
26
+ # hash[:b] = 2
13
27
  class RedundantMerge < Cop
14
28
  AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'
15
29
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
@@ -20,11 +34,11 @@ module RuboCop
20
34
  %<leading_space>send
21
35
  RUBY
22
36
 
23
- def_node_matcher :redundant_merge_candidate, <<-PATTERN
37
+ def_node_matcher :redundant_merge_candidate, <<~PATTERN
24
38
  (send $!nil? :merge! [(hash $...) !kwsplat_type?])
25
39
  PATTERN
26
40
 
27
- def_node_matcher :modifier_flow_control?, <<-PATTERN
41
+ def_node_matcher :modifier_flow_control?, <<~PATTERN
28
42
  [{if while until} modifier_form?]
29
43
  PATTERN
30
44
 
@@ -65,7 +79,8 @@ module RuboCop
65
79
  end
66
80
 
67
81
  def non_redundant_merge?(node, receiver, pairs)
68
- non_redundant_pairs?(receiver, pairs) ||
82
+ pairs.empty? ||
83
+ non_redundant_pairs?(receiver, pairs) ||
69
84
  kwsplat_used?(pairs) ||
70
85
  non_redundant_value_used?(receiver, node)
71
86
  end
@@ -128,11 +143,11 @@ module RuboCop
128
143
  end
129
144
 
130
145
  def indent_width
131
- @config.for_cop('IndentationWidth')['Width'] || 2
146
+ @config.for_cop('Layout/IndentationWidth')['Width'] || 2
132
147
  end
133
148
 
134
149
  def max_key_value_pairs
135
- Integer(cop_config['MaxKeyValuePairs']) || 2
150
+ Integer(cop_config['MaxKeyValuePairs'] || 2)
136
151
  end
137
152
 
138
153
  # A utility class for checking the use of values within an
@@ -167,13 +182,11 @@ module RuboCop
167
182
  end
168
183
 
169
184
  def unwind(receiver)
170
- while receiver.respond_to?(:send_type?) && receiver.send_type?
171
- receiver, = *receiver
172
- end
185
+ receiver, = *receiver while receiver.respond_to?(:send_type?) && receiver.send_type?
173
186
  receiver
174
187
  end
175
188
 
176
- def_node_matcher :each_with_object_node, <<-PATTERN
189
+ def_node_matcher :each_with_object_node, <<~PATTERN
177
190
  (block (send _ :each_with_object _) (args _ $_) ...)
178
191
  PATTERN
179
192
  end
@@ -73,32 +73,28 @@ module RuboCop
73
73
  # end
74
74
  # end
75
75
  class RegexpMatch < Cop
76
- extend TargetRubyVersion
77
-
78
- minimum_target_ruby_version 2.4
79
-
80
76
  # Constants are included in this list because it is unlikely that
81
77
  # someone will store `nil` as a constant and then use it for comparison
82
78
  TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
83
79
  MSG = 'Use `match?` instead of `%<current>s` when `MatchData` ' \
84
80
  'is not used.'
85
81
 
86
- def_node_matcher :match_method?, <<-PATTERN
82
+ def_node_matcher :match_method?, <<~PATTERN
87
83
  {
88
84
  (send _recv :match {regexp str sym})
89
85
  (send {regexp str sym} :match _)
90
86
  }
91
87
  PATTERN
92
88
 
93
- def_node_matcher :match_with_int_arg_method?, <<-PATTERN
89
+ def_node_matcher :match_with_int_arg_method?, <<~PATTERN
94
90
  (send _recv :match _ (int ...))
95
91
  PATTERN
96
92
 
97
- def_node_matcher :match_operator?, <<-PATTERN
93
+ def_node_matcher :match_operator?, <<~PATTERN
98
94
  (send !nil? {:=~ :!~} !nil?)
99
95
  PATTERN
100
96
 
101
- def_node_matcher :match_threequals?, <<-PATTERN
97
+ def_node_matcher :match_threequals?, <<~PATTERN
102
98
  (send (regexp (str _) {(regopt) (regopt _)}) :=== !nil?)
103
99
  PATTERN
104
100
 
@@ -109,7 +105,7 @@ module RuboCop
109
105
  regexp.to_regexp.named_captures.empty?
110
106
  end
111
107
 
112
- MATCH_NODE_PATTERN = <<-PATTERN
108
+ MATCH_NODE_PATTERN = <<~PATTERN
113
109
  {
114
110
  #match_method?
115
111
  #match_with_int_arg_method?
@@ -122,7 +118,7 @@ module RuboCop
122
118
  def_node_matcher :match_node?, MATCH_NODE_PATTERN
123
119
  def_node_search :search_match_nodes, MATCH_NODE_PATTERN
124
120
 
125
- def_node_search :last_matches, <<-PATTERN
121
+ def_node_search :last_matches, <<~PATTERN
126
122
  {
127
123
  (send (const nil? :Regexp) :last_match)
128
124
  (send (const nil? :Regexp) :last_match _)
@@ -256,6 +252,13 @@ module RuboCop
256
252
  def correct_operator(corrector, recv, arg, oper = nil)
257
253
  op_range = correction_range(recv, arg)
258
254
 
255
+ replace_with_match_predicate_method(corrector, recv, arg, op_range)
256
+
257
+ corrector.insert_after(arg.loc.expression, ')') unless op_range.source.end_with?('(')
258
+ corrector.insert_before(recv.loc.expression, '!') if oper == :!~
259
+ end
260
+
261
+ def replace_with_match_predicate_method(corrector, recv, arg, op_range)
259
262
  if TYPES_IMPLEMENTING_MATCH.include?(recv.type)
260
263
  corrector.replace(op_range, '.match?(')
261
264
  elsif TYPES_IMPLEMENTING_MATCH.include?(arg.type)
@@ -264,9 +267,6 @@ module RuboCop
264
267
  else
265
268
  corrector.replace(op_range, '&.match?(')
266
269
  end
267
-
268
- corrector.insert_after(arg.loc.expression, ')')
269
- corrector.insert_before(recv.loc.expression, '!') if oper == :!~
270
270
  end
271
271
 
272
272
  def swap_receiver_and_arg(corrector, recv, arg)