rubocop-performance 1.5.2 → 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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +5 -1
  4. data/config/default.yml +96 -13
  5. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +76 -0
  6. data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
  7. data/lib/rubocop/cop/performance/ancestors_include.rb +48 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +45 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +77 -0
  10. data/lib/rubocop/cop/performance/caller.rb +5 -4
  11. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  12. data/lib/rubocop/cop/performance/casecmp.rb +17 -23
  13. data/lib/rubocop/cop/performance/chain_array_allocation.rb +5 -11
  14. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  15. data/lib/rubocop/cop/performance/compare_with_block.rb +12 -23
  16. data/lib/rubocop/cop/performance/count.rb +14 -17
  17. data/lib/rubocop/cop/performance/delete_prefix.rb +87 -0
  18. data/lib/rubocop/cop/performance/delete_suffix.rb +87 -0
  19. data/lib/rubocop/cop/performance/detect.rb +64 -32
  20. data/lib/rubocop/cop/performance/double_start_end_with.rb +18 -26
  21. data/lib/rubocop/cop/performance/end_with.rb +38 -25
  22. data/lib/rubocop/cop/performance/fixed_size.rb +2 -2
  23. data/lib/rubocop/cop/performance/flat_map.rb +21 -23
  24. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +14 -15
  25. data/lib/rubocop/cop/performance/io_readlines.rb +116 -0
  26. data/lib/rubocop/cop/performance/open_struct.rb +3 -3
  27. data/lib/rubocop/cop/performance/range_include.rb +15 -12
  28. data/lib/rubocop/cop/performance/redundant_block_call.rb +14 -9
  29. data/lib/rubocop/cop/performance/redundant_match.rb +13 -8
  30. data/lib/rubocop/cop/performance/redundant_merge.rb +36 -23
  31. data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
  32. data/lib/rubocop/cop/performance/redundant_string_chars.rb +133 -0
  33. data/lib/rubocop/cop/performance/regexp_match.rb +32 -32
  34. data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
  35. data/lib/rubocop/cop/performance/reverse_first.rb +72 -0
  36. data/lib/rubocop/cop/performance/size.rb +41 -43
  37. data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
  38. data/lib/rubocop/cop/performance/squeeze.rb +66 -0
  39. data/lib/rubocop/cop/performance/start_with.rb +38 -28
  40. data/lib/rubocop/cop/performance/string_include.rb +55 -0
  41. data/lib/rubocop/cop/performance/string_replacement.rb +25 -36
  42. data/lib/rubocop/cop/performance/sum.rb +134 -0
  43. data/lib/rubocop/cop/performance/times_map.rb +12 -19
  44. data/lib/rubocop/cop/performance/unfreeze_string.rb +4 -8
  45. data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -13
  46. data/lib/rubocop/cop/performance_cops.rb +17 -0
  47. data/lib/rubocop/performance/inject.rb +1 -1
  48. data/lib/rubocop/performance/version.rb +1 -1
  49. metadata +41 -11
@@ -17,18 +17,20 @@ module RuboCop
17
17
  # # good
18
18
  # method(str =~ /regex/)
19
19
  # return value unless regex =~ 'str'
20
- class RedundantMatch < Cop
20
+ class RedundantMatch < Base
21
+ extend AutoCorrector
22
+
21
23
  MSG = 'Use `=~` in places where the `MatchData` returned by ' \
22
24
  '`#match` will not be used.'
23
25
 
24
26
  # 'match' is a fairly generic name, so we don't flag it unless we see
25
27
  # a string or regexp literal on one side or the other
26
- def_node_matcher :match_call?, <<-PATTERN
28
+ def_node_matcher :match_call?, <<~PATTERN
27
29
  {(send {str regexp} :match _)
28
30
  (send !nil? :match {str regexp})}
29
31
  PATTERN
30
32
 
31
- def_node_matcher :only_truthiness_matters?, <<-PATTERN
33
+ def_node_matcher :only_truthiness_matters?, <<~PATTERN
32
34
  ^({if while until case while_post until_post} equal?(%0) ...)
33
35
  PATTERN
34
36
 
@@ -37,18 +39,21 @@ module RuboCop
37
39
  (!node.value_used? || only_truthiness_matters?(node)) &&
38
40
  !(node.parent && node.parent.block_type?)
39
41
 
40
- add_offense(node)
42
+ add_offense(node) do |corrector|
43
+ autocorrect(corrector, node)
44
+ end
41
45
  end
42
46
 
43
- def autocorrect(node)
47
+ private
48
+
49
+ def autocorrect(corrector, node)
44
50
  # Regexp#match can take a second argument, but this cop doesn't
45
51
  # register an offense in that case
46
52
  return unless node.first_argument.regexp_type?
47
53
 
48
- new_source =
49
- node.receiver.source + ' =~ ' + node.first_argument.source
54
+ new_source = "#{node.receiver.source} =~ #{node.first_argument.source}"
50
55
 
51
- ->(corrector) { corrector.replace(node.source_range, new_source) }
56
+ corrector.replace(node.source_range, new_source)
52
57
  end
53
58
  end
54
59
  end
@@ -5,12 +5,28 @@ 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)
13
- class RedundantMerge < Cop
23
+ #
24
+ # # good
25
+ # hash[:a] = 1
26
+ # hash[:b] = 2
27
+ class RedundantMerge < Base
28
+ extend AutoCorrector
29
+
14
30
  AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'
15
31
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
16
32
 
@@ -20,28 +36,27 @@ module RuboCop
20
36
  %<leading_space>send
21
37
  RUBY
22
38
 
23
- def_node_matcher :redundant_merge_candidate, <<-PATTERN
39
+ def_node_matcher :redundant_merge_candidate, <<~PATTERN
24
40
  (send $!nil? :merge! [(hash $...) !kwsplat_type?])
25
41
  PATTERN
26
42
 
27
- def_node_matcher :modifier_flow_control?, <<-PATTERN
43
+ def_node_matcher :modifier_flow_control?, <<~PATTERN
28
44
  [{if while until} modifier_form?]
29
45
  PATTERN
30
46
 
31
47
  def on_send(node)
32
48
  each_redundant_merge(node) do |redundant_merge_node|
33
- add_offense(redundant_merge_node)
34
- end
35
- end
36
-
37
- def autocorrect(node)
38
- redundant_merge_candidate(node) do |receiver, pairs|
39
- new_source = to_assignments(receiver, pairs).join("\n")
40
-
41
- if node.parent && pairs.size > 1
42
- correct_multiple_elements(node, node.parent, new_source)
43
- else
44
- correct_single_element(node, new_source)
49
+ message = message(node)
50
+ add_offense(redundant_merge_node, message: message) do |corrector|
51
+ redundant_merge_candidate(node) do |receiver, pairs|
52
+ new_source = to_assignments(receiver, pairs).join("\n")
53
+
54
+ if node.parent && pairs.size > 1
55
+ correct_multiple_elements(corrector, node, node.parent, new_source)
56
+ else
57
+ correct_single_element(corrector, node, new_source)
58
+ end
59
+ end
45
60
  end
46
61
  end
47
62
  end
@@ -84,7 +99,7 @@ module RuboCop
84
99
  !EachWithObjectInspector.new(node, receiver).value_used?
85
100
  end
86
101
 
87
- def correct_multiple_elements(node, parent, new_source)
102
+ def correct_multiple_elements(corrector, node, parent, new_source)
88
103
  if modifier_flow_control?(parent)
89
104
  new_source = rewrite_with_modifier(node, parent, new_source)
90
105
  node = parent
@@ -93,11 +108,11 @@ module RuboCop
93
108
  new_source.gsub!(/\n/, padding)
94
109
  end
95
110
 
96
- ->(corrector) { corrector.replace(node.source_range, new_source) }
111
+ corrector.replace(node.source_range, new_source)
97
112
  end
98
113
 
99
- def correct_single_element(node, new_source)
100
- ->(corrector) { corrector.replace(node.source_range, new_source) }
114
+ def correct_single_element(corrector, node, new_source)
115
+ corrector.replace(node.source_range, new_source)
101
116
  end
102
117
 
103
118
  def to_assignments(receiver, pairs)
@@ -168,13 +183,11 @@ module RuboCop
168
183
  end
169
184
 
170
185
  def unwind(receiver)
171
- while receiver.respond_to?(:send_type?) && receiver.send_type?
172
- receiver, = *receiver
173
- end
186
+ receiver, = *receiver while receiver.respond_to?(:send_type?) && receiver.send_type?
174
187
  receiver
175
188
  end
176
189
 
177
- def_node_matcher :each_with_object_node, <<-PATTERN
190
+ def_node_matcher :each_with_object_node, <<~PATTERN
178
191
  (block (send _ :each_with_object _) (args _ $_) ...)
179
192
  PATTERN
180
193
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `sort { |a, b| a <=> b }`
7
+ # can be replaced with `sort`.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # array.sort { |a, b| a <=> b }
12
+ #
13
+ # # good
14
+ # array.sort
15
+ #
16
+ class RedundantSortBlock < Base
17
+ include SortBlock
18
+ extend AutoCorrector
19
+
20
+ MSG = 'Use `sort` instead of `%<bad_method>s`.'
21
+
22
+ def on_block(node)
23
+ return unless (send, var_a, var_b, body = sort_with_block?(node))
24
+
25
+ replaceable_body?(body, var_a, var_b) do
26
+ range = sort_range(send, node)
27
+
28
+ add_offense(range, message: message(var_a, var_b)) do |corrector|
29
+ corrector.replace(range, 'sort')
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def message(var_a, var_b)
37
+ bad_method = "sort { |#{var_a}, #{var_b}| #{var_a} <=> #{var_b} }"
38
+ format(MSG, bad_method: bad_method)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop checks for redundant `String#chars`.
7
+ #
8
+ # @example
9
+ # # bad
10
+ # str.chars[0..2]
11
+ # str.chars.slice(0..2)
12
+ #
13
+ # # good
14
+ # str[0..2].chars
15
+ #
16
+ # # bad
17
+ # str.chars.first
18
+ # str.chars.first(2)
19
+ # str.chars.last
20
+ # str.chars.last(2)
21
+ #
22
+ # # good
23
+ # str[0]
24
+ # str[0...2].chars
25
+ # str[-1]
26
+ # str[-2..-1].chars
27
+ #
28
+ # # bad
29
+ # str.chars.take(2)
30
+ # str.chars.drop(2)
31
+ # str.chars.length
32
+ # str.chars.size
33
+ # str.chars.empty?
34
+ #
35
+ # # good
36
+ # str[0...2].chars
37
+ # str[2..-1].chars
38
+ # str.length
39
+ # str.size
40
+ # str.empty?
41
+ #
42
+ class RedundantStringChars < Base
43
+ include RangeHelp
44
+ extend AutoCorrector
45
+
46
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
47
+ REPLACEABLE_METHODS = %i[[] slice first last take drop length size empty?].freeze
48
+
49
+ def_node_matcher :redundant_chars_call?, <<~PATTERN
50
+ (send $(send _ :chars) $#replaceable_method? $...)
51
+ PATTERN
52
+
53
+ def on_send(node)
54
+ return unless (receiver, method, args = redundant_chars_call?(node))
55
+
56
+ range = offense_range(receiver, node)
57
+ message = build_message(method, args)
58
+
59
+ add_offense(range, message: message) do |corrector|
60
+ range = correction_range(receiver, node)
61
+ replacement = build_good_method(method, args)
62
+
63
+ corrector.replace(range, replacement)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def replaceable_method?(method_name)
70
+ REPLACEABLE_METHODS.include?(method_name)
71
+ end
72
+
73
+ def offense_range(receiver, node)
74
+ range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
75
+ end
76
+
77
+ def correction_range(receiver, node)
78
+ range_between(receiver.loc.dot.begin_pos, node.loc.expression.end_pos)
79
+ end
80
+
81
+ def build_message(method, args)
82
+ good_method = build_good_method(method, args)
83
+ bad_method = build_bad_method(method, args)
84
+ format(MSG, good_method: good_method, bad_method: bad_method)
85
+ end
86
+
87
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
88
+ def build_good_method(method, args)
89
+ case method
90
+ when :[], :slice
91
+ "[#{build_call_args(args)}].chars"
92
+ when :first
93
+ if args.any?
94
+ "[0...#{args.first.source}].chars"
95
+ else
96
+ '[0]'
97
+ end
98
+ when :last
99
+ if args.any?
100
+ "[-#{args.first.source}..-1].chars"
101
+ else
102
+ '[-1]'
103
+ end
104
+ when :take
105
+ "[0...#{args.first.source}].chars"
106
+ when :drop
107
+ "[#{args.first.source}..-1].chars"
108
+ else
109
+ ".#{method}"
110
+ end
111
+ end
112
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
113
+
114
+ def build_bad_method(method, args)
115
+ case method
116
+ when :[]
117
+ "chars[#{build_call_args(args)}]"
118
+ else
119
+ if args.any?
120
+ "chars.#{method}(#{build_call_args(args)})"
121
+ else
122
+ "chars.#{method}"
123
+ end
124
+ end
125
+ end
126
+
127
+ def build_call_args(call_args_node)
128
+ call_args_node.map(&:source).join(', ')
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -72,10 +72,8 @@ module RuboCop
72
72
  # do_something($~)
73
73
  # end
74
74
  # end
75
- class RegexpMatch < Cop
76
- extend TargetRubyVersion
77
-
78
- minimum_target_ruby_version 2.4
75
+ class RegexpMatch < Base
76
+ extend AutoCorrector
79
77
 
80
78
  # Constants are included in this list because it is unlikely that
81
79
  # someone will store `nil` as a constant and then use it for comparison
@@ -83,22 +81,22 @@ module RuboCop
83
81
  MSG = 'Use `match?` instead of `%<current>s` when `MatchData` ' \
84
82
  'is not used.'
85
83
 
86
- def_node_matcher :match_method?, <<-PATTERN
84
+ def_node_matcher :match_method?, <<~PATTERN
87
85
  {
88
86
  (send _recv :match {regexp str sym})
89
87
  (send {regexp str sym} :match _)
90
88
  }
91
89
  PATTERN
92
90
 
93
- def_node_matcher :match_with_int_arg_method?, <<-PATTERN
91
+ def_node_matcher :match_with_int_arg_method?, <<~PATTERN
94
92
  (send _recv :match _ (int ...))
95
93
  PATTERN
96
94
 
97
- def_node_matcher :match_operator?, <<-PATTERN
95
+ def_node_matcher :match_operator?, <<~PATTERN
98
96
  (send !nil? {:=~ :!~} !nil?)
99
97
  PATTERN
100
98
 
101
- def_node_matcher :match_threequals?, <<-PATTERN
99
+ def_node_matcher :match_threequals?, <<~PATTERN
102
100
  (send (regexp (str _) {(regopt) (regopt _)}) :=== !nil?)
103
101
  PATTERN
104
102
 
@@ -109,7 +107,7 @@ module RuboCop
109
107
  regexp.to_regexp.named_captures.empty?
110
108
  end
111
109
 
112
- MATCH_NODE_PATTERN = <<-PATTERN
110
+ MATCH_NODE_PATTERN = <<~PATTERN
113
111
  {
114
112
  #match_method?
115
113
  #match_with_int_arg_method?
@@ -122,7 +120,7 @@ module RuboCop
122
120
  def_node_matcher :match_node?, MATCH_NODE_PATTERN
123
121
  def_node_search :search_match_nodes, MATCH_NODE_PATTERN
124
122
 
125
- def_node_search :last_matches, <<-PATTERN
123
+ def_node_search :last_matches, <<~PATTERN
126
124
  {
127
125
  (send (const nil? :Regexp) :last_match)
128
126
  (send (const nil? :Regexp) :last_match _)
@@ -145,27 +143,28 @@ module RuboCop
145
143
  end
146
144
  end
147
145
 
148
- def autocorrect(node)
149
- lambda do |corrector|
150
- if match_method?(node) || match_with_int_arg_method?(node)
151
- corrector.replace(node.loc.selector, 'match?')
152
- elsif match_operator?(node) || match_threequals?(node)
153
- recv, oper, arg = *node
154
- correct_operator(corrector, recv, arg, oper)
155
- elsif match_with_lvasgn?(node)
156
- recv, arg = *node
157
- correct_operator(corrector, recv, arg)
158
- end
159
- end
160
- end
161
-
162
146
  private
163
147
 
164
148
  def check_condition(cond)
165
149
  match_node?(cond) do
166
150
  return if last_match_used?(cond)
167
151
 
168
- add_offense(cond)
152
+ message = message(cond)
153
+ add_offense(cond, message: message) do |corrector|
154
+ autocorrect(corrector, cond)
155
+ end
156
+ end
157
+ end
158
+
159
+ def autocorrect(corrector, node)
160
+ if match_method?(node) || match_with_int_arg_method?(node)
161
+ corrector.replace(node.loc.selector, 'match?')
162
+ elsif match_operator?(node) || match_threequals?(node)
163
+ recv, oper, arg = *node
164
+ correct_operator(corrector, recv, arg, oper)
165
+ elsif match_with_lvasgn?(node)
166
+ recv, arg = *node
167
+ correct_operator(corrector, recv, arg)
169
168
  end
170
169
  end
171
170
 
@@ -235,10 +234,7 @@ module RuboCop
235
234
 
236
235
  def scope_root(node)
237
236
  node.each_ancestor.find do |ancestor|
238
- ancestor.def_type? ||
239
- ancestor.defs_type? ||
240
- ancestor.class_type? ||
241
- ancestor.module_type?
237
+ ancestor.def_type? || ancestor.defs_type? || ancestor.class_type? || ancestor.module_type?
242
238
  end
243
239
  end
244
240
 
@@ -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)