rubocop-performance 1.5.2 → 1.8.1

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