rubocop-performance 1.4.1 → 1.6.1

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