rubocop-performance 1.5.1 → 1.7.1

Sign up to get free protection for your applications and to get access to all the features.
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 +78 -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 +47 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +50 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +87 -0
  10. data/lib/rubocop/cop/performance/caller.rb +2 -2
  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 +30 -12
  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 +20 -7
  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 +30 -15
  38. data/lib/rubocop/cop/performance/string_include.rb +59 -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
@@ -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