rubocop-performance 1.5.0 → 1.7.0

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +5 -1
  4. data/config/default.yml +75 -6
  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 +45 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +43 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +87 -0
  10. data/lib/rubocop/cop/performance/caller.rb +3 -3
  11. data/lib/rubocop/cop/performance/casecmp.rb +5 -3
  12. data/lib/rubocop/cop/performance/chain_array_allocation.rb +1 -1
  13. data/lib/rubocop/cop/performance/compare_with_block.rb +2 -2
  14. data/lib/rubocop/cop/performance/count.rb +3 -3
  15. data/lib/rubocop/cop/performance/delete_prefix.rb +96 -0
  16. data/lib/rubocop/cop/performance/delete_suffix.rb +96 -0
  17. data/lib/rubocop/cop/performance/detect.rb +1 -1
  18. data/lib/rubocop/cop/performance/double_start_end_with.rb +2 -2
  19. data/lib/rubocop/cop/performance/end_with.rb +36 -13
  20. data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
  21. data/lib/rubocop/cop/performance/flat_map.rb +1 -1
  22. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +1 -1
  23. data/lib/rubocop/cop/performance/io_readlines.rb +127 -0
  24. data/lib/rubocop/cop/performance/open_struct.rb +1 -1
  25. data/lib/rubocop/cop/performance/range_include.rb +10 -8
  26. data/lib/rubocop/cop/performance/redundant_block_call.rb +3 -3
  27. data/lib/rubocop/cop/performance/redundant_match.rb +2 -2
  28. data/lib/rubocop/cop/performance/redundant_merge.rb +21 -8
  29. data/lib/rubocop/cop/performance/redundant_sort_block.rb +53 -0
  30. data/lib/rubocop/cop/performance/redundant_string_chars.rb +137 -0
  31. data/lib/rubocop/cop/performance/regexp_match.rb +13 -13
  32. data/lib/rubocop/cop/performance/reverse_each.rb +3 -2
  33. data/lib/rubocop/cop/performance/reverse_first.rb +78 -0
  34. data/lib/rubocop/cop/performance/size.rb +35 -37
  35. data/lib/rubocop/cop/performance/sort_reverse.rb +54 -0
  36. data/lib/rubocop/cop/performance/squeeze.rb +70 -0
  37. data/lib/rubocop/cop/performance/start_with.rb +36 -16
  38. data/lib/rubocop/cop/performance/string_include.rb +57 -0
  39. data/lib/rubocop/cop/performance/string_replacement.rb +4 -11
  40. data/lib/rubocop/cop/performance/times_map.rb +1 -1
  41. data/lib/rubocop/cop/performance/unfreeze_string.rb +3 -7
  42. data/lib/rubocop/cop/performance/uri_default_parser.rb +1 -1
  43. data/lib/rubocop/cop/performance_cops.rb +15 -0
  44. data/lib/rubocop/performance/inject.rb +1 -1
  45. data/lib/rubocop/performance/version.rb +1 -1
  46. metadata +25 -11
@@ -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
 
@@ -3,18 +3,19 @@
3
3
  module RuboCop
4
4
  module Cop
5
5
  module Performance
6
- # This cop identifies uses of `Range#include?`, which iterates over each
6
+ # This cop 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
10
10
  # is wanted.
11
11
  #
12
- # This cop is `Safe: false` by default because `Range#include?` and
12
+ # This cop is `Safe: false` by default because `Range#include?` (or `Range#member?`) and
13
13
  # `Range#cover?` are not equivalent behaviour.
14
14
  #
15
15
  # @example
16
16
  # # bad
17
17
  # ('a'..'z').include?('b') # => true
18
+ # ('a'..'z').member?('b') # => true
18
19
  #
19
20
  # # good
20
21
  # ('a'..'z').cover?('b') # => true
@@ -24,21 +25,22 @@ module RuboCop
24
25
  #
25
26
  # ('a'..'z').cover?('yellow') # => true
26
27
  class RangeInclude < Cop
27
- MSG = 'Use `Range#cover?` instead of `Range#include?`.'
28
+ MSG = 'Use `Range#cover?` instead of `Range#%<bad_method>s`.'
28
29
 
29
30
  # TODO: If we traced out assignments of variables to their uses, we
30
31
  # might pick up on a few more instances of this issue
31
32
  # Right now, we only detect direct calls on a Range literal
32
33
  # (We don't even catch it if the Range is in double parens)
33
34
 
34
- def_node_matcher :range_include, <<-PATTERN
35
- (send {irange erange (begin {irange erange})} :include? ...)
35
+ def_node_matcher :range_include, <<~PATTERN
36
+ (send {irange erange (begin {irange erange})} ${:include? :member?} ...)
36
37
  PATTERN
37
38
 
38
39
  def on_send(node)
39
- return unless range_include(node)
40
-
41
- add_offense(node, location: :selector)
40
+ range_include(node) do |bad_method|
41
+ message = format(MSG, bad_method: bad_method)
42
+ add_offense(node, location: :selector, message: message)
43
+ end
42
44
  end
43
45
 
44
46
  def autocorrect(node)
@@ -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,7 +143,7 @@ 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
@@ -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
@@ -0,0 +1,53 @@
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 < Cop
17
+ include SortBlock
18
+
19
+ MSG = 'Use `sort` instead of `%<bad_method>s`.'
20
+
21
+ def on_block(node)
22
+ sort_with_block?(node) do |send, var_a, var_b, body|
23
+ replaceable_body?(body, var_a, var_b) do
24
+ range = sort_range(send, node)
25
+
26
+ add_offense(
27
+ node,
28
+ location: range,
29
+ message: message(var_a, var_b)
30
+ )
31
+ end
32
+ end
33
+ end
34
+
35
+ def autocorrect(node)
36
+ sort_with_block?(node) do |send, _var_a, _var_b, _body|
37
+ lambda do |corrector|
38
+ range = sort_range(send, node)
39
+ corrector.replace(range, 'sort')
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def message(var_a, var_b)
47
+ bad_method = "sort { |#{var_a}, #{var_b}| #{var_a} <=> #{var_b} }"
48
+ format(MSG, bad_method: bad_method)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,137 @@
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 < Cop
43
+ include RangeHelp
44
+
45
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
46
+ REPLACEABLE_METHODS = %i[[] slice first last take drop length size empty?].freeze
47
+
48
+ def_node_matcher :redundant_chars_call?, <<~PATTERN
49
+ (send $(send _ :chars) $#replaceable_method? $...)
50
+ PATTERN
51
+
52
+ def on_send(node)
53
+ redundant_chars_call?(node) do |receiver, method, args|
54
+ range = offense_range(receiver, node)
55
+ message = build_message(method, args)
56
+ add_offense(node, location: range, message: message)
57
+ end
58
+ end
59
+
60
+ def autocorrect(node)
61
+ redundant_chars_call?(node) do |receiver, method, args|
62
+ range = correction_range(receiver, node)
63
+ replacement = build_good_method(method, args)
64
+
65
+ lambda do |corrector|
66
+ corrector.replace(range, replacement)
67
+ end
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def replaceable_method?(method_name)
74
+ REPLACEABLE_METHODS.include?(method_name)
75
+ end
76
+
77
+ def offense_range(receiver, node)
78
+ range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
79
+ end
80
+
81
+ def correction_range(receiver, node)
82
+ range_between(receiver.loc.dot.begin_pos, node.loc.expression.end_pos)
83
+ end
84
+
85
+ def build_message(method, args)
86
+ good_method = build_good_method(method, args)
87
+ bad_method = build_bad_method(method, args)
88
+ format(MSG, good_method: good_method, bad_method: bad_method)
89
+ end
90
+
91
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
92
+ def build_good_method(method, args)
93
+ case method
94
+ when :[], :slice
95
+ "[#{build_call_args(args)}].chars"
96
+ when :first
97
+ if args.any?
98
+ "[0...#{args.first.source}].chars"
99
+ else
100
+ '[0]'
101
+ end
102
+ when :last
103
+ if args.any?
104
+ "[-#{args.first.source}..-1].chars"
105
+ else
106
+ '[-1]'
107
+ end
108
+ when :take
109
+ "[0...#{args.first.source}].chars"
110
+ when :drop
111
+ "[#{args.first.source}..-1].chars"
112
+ else
113
+ ".#{method}"
114
+ end
115
+ end
116
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
117
+
118
+ def build_bad_method(method, args)
119
+ case method
120
+ when :[]
121
+ "chars[#{build_call_args(args)}]"
122
+ else
123
+ if args.any?
124
+ "chars.#{method}(#{build_call_args(args)})"
125
+ else
126
+ "chars.#{method}"
127
+ end
128
+ end
129
+ end
130
+
131
+ def build_call_args(call_args_node)
132
+ call_args_node.map(&:source).join(', ')
133
+ end
134
+ end
135
+ end
136
+ end
137
+ 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)
@@ -18,7 +18,7 @@ module RuboCop
18
18
  MSG = 'Use `reverse_each` instead of `reverse.each`.'
19
19
  UNDERSCORE = '_'
20
20
 
21
- def_node_matcher :reverse_each?, <<-MATCHER
21
+ def_node_matcher :reverse_each?, <<~MATCHER
22
22
  (send $(send _ :reverse) :each)
23
23
  MATCHER
24
24
 
@@ -34,7 +34,8 @@ module RuboCop
34
34
  end
35
35
 
36
36
  def autocorrect(node)
37
- ->(corrector) { corrector.replace(node.loc.dot, UNDERSCORE) }
37
+ range = range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
38
+ ->(corrector) { corrector.replace(range, UNDERSCORE) }
38
39
  end
39
40
  end
40
41
  end