rubocop-performance 1.5.2 → 1.8.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 (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 +30 -27
  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 +129 -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 +27 -11
@@ -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)
@@ -12,13 +12,14 @@ module RuboCop
12
12
  #
13
13
  # # good
14
14
  # [].reverse_each
15
- class ReverseEach < Cop
15
+ class ReverseEach < Base
16
16
  include RangeHelp
17
+ extend AutoCorrector
17
18
 
18
19
  MSG = 'Use `reverse_each` instead of `reverse.each`.'
19
20
  UNDERSCORE = '_'
20
21
 
21
- def_node_matcher :reverse_each?, <<-MATCHER
22
+ def_node_matcher :reverse_each?, <<~MATCHER
22
23
  (send $(send _ :reverse) :each)
23
24
  MATCHER
24
25
 
@@ -29,12 +30,16 @@ module RuboCop
29
30
 
30
31
  range = range_between(location_of_reverse, end_location)
31
32
 
32
- add_offense(node, location: range)
33
+ add_offense(range) do |corrector|
34
+ corrector.replace(replacement_range(node), UNDERSCORE)
35
+ end
33
36
  end
34
37
  end
35
38
 
36
- def autocorrect(node)
37
- ->(corrector) { corrector.replace(node.loc.dot, UNDERSCORE) }
39
+ private
40
+
41
+ def replacement_range(node)
42
+ range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
38
43
  end
39
44
  end
40
45
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module Performance
6
+ # This cop identifies places where `reverse.first(n)` and `reverse.first`
7
+ # can be replaced by `last(n).reverse` and `last`.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # array.reverse.first(5)
13
+ # array.reverse.first
14
+ #
15
+ # # good
16
+ # array.last(5).reverse
17
+ # array.last
18
+ #
19
+ class ReverseFirst < Base
20
+ include RangeHelp
21
+ extend AutoCorrector
22
+
23
+ MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
24
+
25
+ def_node_matcher :reverse_first_candidate?, <<~PATTERN
26
+ (send $(send _ :reverse) :first (int _)?)
27
+ PATTERN
28
+
29
+ def on_send(node)
30
+ reverse_first_candidate?(node) do |receiver|
31
+ range = correction_range(receiver, node)
32
+ message = build_message(node)
33
+
34
+ add_offense(range, message: message) do |corrector|
35
+ replacement = build_good_method(node)
36
+
37
+ corrector.replace(range, replacement)
38
+ end
39
+ end
40
+ end
41
+
42
+ private
43
+
44
+ def correction_range(receiver, node)
45
+ range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
46
+ end
47
+
48
+ def build_message(node)
49
+ good_method = build_good_method(node)
50
+ bad_method = build_bad_method(node)
51
+ format(MSG, good_method: good_method, bad_method: bad_method)
52
+ end
53
+
54
+ def build_good_method(node)
55
+ if node.arguments?
56
+ "last(#{node.arguments.first.source}).reverse"
57
+ else
58
+ 'last'
59
+ end
60
+ end
61
+
62
+ def build_bad_method(node)
63
+ if node.arguments?
64
+ "reverse.first(#{node.arguments.first.source})"
65
+ else
66
+ 'reverse.first'
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -9,67 +9,65 @@ module RuboCop
9
9
  # @example
10
10
  # # bad
11
11
  # [1, 2, 3].count
12
+ # (1..3).to_a.count
13
+ # Array[*1..3].count
14
+ # Array(1..3).count
12
15
  #
13
16
  # # bad
14
17
  # {a: 1, b: 2, c: 3}.count
18
+ # [[:foo, :bar], [1, 2]].to_h.count
19
+ # Hash[*('a'..'z')].count
20
+ # Hash(key: :value).count
15
21
  #
16
22
  # # good
17
23
  # [1, 2, 3].size
24
+ # (1..3).to_a.size
25
+ # Array[*1..3].size
26
+ # Array(1..3).size
18
27
  #
19
28
  # # good
20
29
  # {a: 1, b: 2, c: 3}.size
30
+ # [[:foo, :bar], [1, 2]].to_h.size
31
+ # Hash[*('a'..'z')].size
32
+ # Hash(key: :value).size
21
33
  #
22
34
  # # good
23
35
  # [1, 2, 3].count { |e| e > 2 }
24
36
  # TODO: Add advanced detection of variables that could
25
37
  # have been assigned to an array or a hash.
26
- class Size < Cop
27
- MSG = 'Use `size` instead of `count`.'
28
-
29
- def on_send(node)
30
- return unless eligible_node?(node)
31
-
32
- add_offense(node, location: :selector)
33
- end
34
-
35
- def autocorrect(node)
36
- ->(corrector) { corrector.replace(node.loc.selector, 'size') }
37
- end
38
-
39
- private
40
-
41
- def eligible_node?(node)
42
- return false unless node.method?(:count) && !node.arguments?
43
-
44
- eligible_receiver?(node.receiver) && !allowed_parent?(node.parent)
45
- end
38
+ class Size < Base
39
+ extend AutoCorrector
46
40
 
47
- def eligible_receiver?(node)
48
- return false unless node
49
-
50
- array?(node) || hash?(node)
51
- end
52
-
53
- def allowed_parent?(node)
54
- node&.block_type?
55
- end
56
-
57
- def array?(node)
58
- return true if node.array_type?
59
- return false unless node.send_type?
60
-
61
- _, constant = *node.receiver
62
-
63
- constant == :Array || node.method?(:to_a)
64
- end
41
+ MSG = 'Use `size` instead of `count`.'
65
42
 
66
- def hash?(node)
67
- return true if node.hash_type?
68
- return false unless node.send_type?
43
+ def_node_matcher :array?, <<~PATTERN
44
+ {
45
+ [!nil? array_type?]
46
+ (send _ :to_a)
47
+ (send (const nil? :Array) :[] _)
48
+ (send nil? :Array _)
49
+ }
50
+ PATTERN
51
+
52
+ def_node_matcher :hash?, <<~PATTERN
53
+ {
54
+ [!nil? hash_type?]
55
+ (send _ :to_h)
56
+ (send (const nil? :Hash) :[] _)
57
+ (send nil? :Hash _)
58
+ }
59
+ PATTERN
60
+
61
+ def_node_matcher :count?, <<~PATTERN
62
+ (send {#array? #hash?} :count)
63
+ PATTERN
69
64
 
70
- _, constant = *node.receiver
65
+ def on_send(node)
66
+ return if node.parent&.block_type? || !count?(node)
71
67
 
72
- constant == :Hash || node.method?(:to_h)
68
+ add_offense(node.loc.selector) do |corrector|
69
+ corrector.replace(node.loc.selector, 'size')
70
+ end
73
71
  end
74
72
  end
75
73
  end