rubocop-performance 1.5.2 → 1.8.0

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 +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