rubocop-performance 1.6.1 → 1.9.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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -0
  3. data/config/default.yml +95 -8
  4. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
  5. data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
  6. data/lib/rubocop/cop/performance/ancestors_include.rb +49 -0
  7. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +74 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +46 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +9 -18
  10. data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +52 -0
  11. data/lib/rubocop/cop/performance/caller.rb +14 -15
  12. data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
  13. data/lib/rubocop/cop/performance/casecmp.rb +13 -20
  14. data/lib/rubocop/cop/performance/chain_array_allocation.rb +4 -10
  15. data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
  16. data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
  17. data/lib/rubocop/cop/performance/constant_regexp.rb +68 -0
  18. data/lib/rubocop/cop/performance/count.rb +14 -16
  19. data/lib/rubocop/cop/performance/delete_prefix.rb +14 -22
  20. data/lib/rubocop/cop/performance/delete_suffix.rb +14 -22
  21. data/lib/rubocop/cop/performance/detect.rb +65 -32
  22. data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
  23. data/lib/rubocop/cop/performance/end_with.rb +9 -13
  24. data/lib/rubocop/cop/performance/fixed_size.rb +2 -1
  25. data/lib/rubocop/cop/performance/flat_map.rb +21 -22
  26. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +15 -14
  27. data/lib/rubocop/cop/performance/io_readlines.rb +112 -0
  28. data/lib/rubocop/cop/performance/method_object_as_block.rb +32 -0
  29. data/lib/rubocop/cop/performance/open_struct.rb +3 -2
  30. data/lib/rubocop/cop/performance/range_include.rb +15 -11
  31. data/lib/rubocop/cop/performance/redundant_block_call.rb +15 -10
  32. data/lib/rubocop/cop/performance/redundant_match.rb +12 -6
  33. data/lib/rubocop/cop/performance/redundant_merge.rb +19 -17
  34. data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
  35. data/lib/rubocop/cop/performance/redundant_string_chars.rb +129 -0
  36. data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
  37. data/lib/rubocop/cop/performance/reverse_each.rb +10 -5
  38. data/lib/rubocop/cop/performance/reverse_first.rb +73 -0
  39. data/lib/rubocop/cop/performance/size.rb +42 -43
  40. data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
  41. data/lib/rubocop/cop/performance/squeeze.rb +67 -0
  42. data/lib/rubocop/cop/performance/start_with.rb +9 -13
  43. data/lib/rubocop/cop/performance/string_include.rb +56 -0
  44. data/lib/rubocop/cop/performance/string_replacement.rb +24 -27
  45. data/lib/rubocop/cop/performance/sum.rb +236 -0
  46. data/lib/rubocop/cop/performance/times_map.rb +12 -18
  47. data/lib/rubocop/cop/performance/unfreeze_string.rb +20 -2
  48. data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -12
  49. data/lib/rubocop/cop/performance_cops.rb +16 -0
  50. data/lib/rubocop/performance/version.rb +6 -1
  51. metadata +35 -13
@@ -17,9 +17,12 @@ 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.'
25
+ RESTRICT_ON_SEND = %i[match].freeze
23
26
 
24
27
  # 'match' is a fairly generic name, so we don't flag it unless we see
25
28
  # a string or regexp literal on one side or the other
@@ -37,18 +40,21 @@ module RuboCop
37
40
  (!node.value_used? || only_truthiness_matters?(node)) &&
38
41
  !(node.parent && node.parent.block_type?)
39
42
 
40
- add_offense(node)
43
+ add_offense(node) do |corrector|
44
+ autocorrect(corrector, node)
45
+ end
41
46
  end
42
47
 
43
- def autocorrect(node)
48
+ private
49
+
50
+ def autocorrect(corrector, node)
44
51
  # Regexp#match can take a second argument, but this cop doesn't
45
52
  # register an offense in that case
46
53
  return unless node.first_argument.regexp_type?
47
54
 
48
- new_source =
49
- node.receiver.source + ' =~ ' + node.first_argument.source
55
+ new_source = "#{node.receiver.source} =~ #{node.first_argument.source}"
50
56
 
51
- ->(corrector) { corrector.replace(node.source_range, new_source) }
57
+ corrector.replace(node.source_range, new_source)
52
58
  end
53
59
  end
54
60
  end
@@ -24,9 +24,12 @@ module RuboCop
24
24
  # # good
25
25
  # hash[:a] = 1
26
26
  # hash[:b] = 2
27
- class RedundantMerge < Cop
27
+ class RedundantMerge < Base
28
+ extend AutoCorrector
29
+
28
30
  AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'
29
31
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
32
+ RESTRICT_ON_SEND = %i[merge!].freeze
30
33
 
31
34
  WITH_MODIFIER_CORRECTION = <<~RUBY
32
35
  %<keyword>s %<condition>s
@@ -44,18 +47,17 @@ module RuboCop
44
47
 
45
48
  def on_send(node)
46
49
  each_redundant_merge(node) do |redundant_merge_node|
47
- add_offense(redundant_merge_node)
48
- end
49
- end
50
-
51
- def autocorrect(node)
52
- redundant_merge_candidate(node) do |receiver, pairs|
53
- new_source = to_assignments(receiver, pairs).join("\n")
54
-
55
- if node.parent && pairs.size > 1
56
- correct_multiple_elements(node, node.parent, new_source)
57
- else
58
- correct_single_element(node, new_source)
50
+ message = message(node)
51
+ add_offense(redundant_merge_node, message: message) do |corrector|
52
+ redundant_merge_candidate(node) do |receiver, pairs|
53
+ new_source = to_assignments(receiver, pairs).join("\n")
54
+
55
+ if node.parent && pairs.size > 1
56
+ correct_multiple_elements(corrector, node, node.parent, new_source)
57
+ else
58
+ correct_single_element(corrector, node, new_source)
59
+ end
60
+ end
59
61
  end
60
62
  end
61
63
  end
@@ -98,7 +100,7 @@ module RuboCop
98
100
  !EachWithObjectInspector.new(node, receiver).value_used?
99
101
  end
100
102
 
101
- def correct_multiple_elements(node, parent, new_source)
103
+ def correct_multiple_elements(corrector, node, parent, new_source)
102
104
  if modifier_flow_control?(parent)
103
105
  new_source = rewrite_with_modifier(node, parent, new_source)
104
106
  node = parent
@@ -107,11 +109,11 @@ module RuboCop
107
109
  new_source.gsub!(/\n/, padding)
108
110
  end
109
111
 
110
- ->(corrector) { corrector.replace(node.source_range, new_source) }
112
+ corrector.replace(node.source_range, new_source)
111
113
  end
112
114
 
113
- def correct_single_element(node, new_source)
114
- ->(corrector) { corrector.replace(node.source_range, new_source) }
115
+ def correct_single_element(corrector, node, new_source)
116
+ corrector.replace(node.source_range, new_source)
115
117
  end
116
118
 
117
119
  def to_assignments(receiver, pairs)
@@ -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,129 @@
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
+ RESTRICT_ON_SEND = %i[[] slice first last take drop length size empty?].freeze
48
+
49
+ def_node_matcher :redundant_chars_call?, <<~PATTERN
50
+ (send $(send _ :chars) $_ $...)
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 offense_range(receiver, node)
70
+ range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
71
+ end
72
+
73
+ def correction_range(receiver, node)
74
+ range_between(receiver.loc.dot.begin_pos, node.loc.expression.end_pos)
75
+ end
76
+
77
+ def build_message(method, args)
78
+ good_method = build_good_method(method, args)
79
+ bad_method = build_bad_method(method, args)
80
+ format(MSG, good_method: good_method, bad_method: bad_method)
81
+ end
82
+
83
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
84
+ def build_good_method(method, args)
85
+ case method
86
+ when :[], :slice
87
+ "[#{build_call_args(args)}].chars"
88
+ when :first
89
+ if args.any?
90
+ "[0...#{args.first.source}].chars"
91
+ else
92
+ '[0]'
93
+ end
94
+ when :last
95
+ if args.any?
96
+ "[-#{args.first.source}..-1].chars"
97
+ else
98
+ '[-1]'
99
+ end
100
+ when :take
101
+ "[0...#{args.first.source}].chars"
102
+ when :drop
103
+ "[#{args.first.source}..-1].chars"
104
+ else
105
+ ".#{method}"
106
+ end
107
+ end
108
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
109
+
110
+ def build_bad_method(method, args)
111
+ case method
112
+ when :[]
113
+ "chars[#{build_call_args(args)}]"
114
+ else
115
+ if args.any?
116
+ "chars.#{method}(#{build_call_args(args)})"
117
+ else
118
+ "chars.#{method}"
119
+ end
120
+ end
121
+ end
122
+
123
+ def build_call_args(call_args_node)
124
+ call_args_node.map(&:source).join(', ')
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -72,7 +72,9 @@ module RuboCop
72
72
  # do_something($~)
73
73
  # end
74
74
  # end
75
- class RegexpMatch < Cop
75
+ class RegexpMatch < Base
76
+ extend AutoCorrector
77
+
76
78
  # Constants are included in this list because it is unlikely that
77
79
  # someone will store `nil` as a constant and then use it for comparison
78
80
  TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
@@ -141,27 +143,28 @@ module RuboCop
141
143
  end
142
144
  end
143
145
 
144
- def autocorrect(node)
145
- lambda do |corrector|
146
- if match_method?(node) || match_with_int_arg_method?(node)
147
- corrector.replace(node.loc.selector, 'match?')
148
- elsif match_operator?(node) || match_threequals?(node)
149
- recv, oper, arg = *node
150
- correct_operator(corrector, recv, arg, oper)
151
- elsif match_with_lvasgn?(node)
152
- recv, arg = *node
153
- correct_operator(corrector, recv, arg)
154
- end
155
- end
156
- end
157
-
158
146
  private
159
147
 
160
148
  def check_condition(cond)
161
149
  match_node?(cond) do
162
150
  return if last_match_used?(cond)
163
151
 
164
- 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)
165
168
  end
166
169
  end
167
170
 
@@ -231,10 +234,7 @@ module RuboCop
231
234
 
232
235
  def scope_root(node)
233
236
  node.each_ancestor.find do |ancestor|
234
- ancestor.def_type? ||
235
- ancestor.defs_type? ||
236
- ancestor.class_type? ||
237
- ancestor.module_type?
237
+ ancestor.def_type? || ancestor.defs_type? || ancestor.class_type? || ancestor.module_type?
238
238
  end
239
239
  end
240
240
 
@@ -12,10 +12,12 @@ 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`.'
20
+ RESTRICT_ON_SEND = %i[each].freeze
19
21
  UNDERSCORE = '_'
20
22
 
21
23
  def_node_matcher :reverse_each?, <<~MATCHER
@@ -29,13 +31,16 @@ module RuboCop
29
31
 
30
32
  range = range_between(location_of_reverse, end_location)
31
33
 
32
- add_offense(node, location: range)
34
+ add_offense(range) do |corrector|
35
+ corrector.replace(replacement_range(node), UNDERSCORE)
36
+ end
33
37
  end
34
38
  end
35
39
 
36
- def autocorrect(node)
37
- range = range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
38
- ->(corrector) { corrector.replace(range, UNDERSCORE) }
40
+ private
41
+
42
+ def replacement_range(node)
43
+ range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
39
44
  end
40
45
  end
41
46
  end
@@ -0,0 +1,73 @@
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
+ RESTRICT_ON_SEND = %i[first].freeze
25
+
26
+ def_node_matcher :reverse_first_candidate?, <<~PATTERN
27
+ (send $(send _ :reverse) :first (int _)?)
28
+ PATTERN
29
+
30
+ def on_send(node)
31
+ reverse_first_candidate?(node) do |receiver|
32
+ range = correction_range(receiver, node)
33
+ message = build_message(node)
34
+
35
+ add_offense(range, message: message) do |corrector|
36
+ replacement = build_good_method(node)
37
+
38
+ corrector.replace(range, replacement)
39
+ end
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def correction_range(receiver, node)
46
+ range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
47
+ end
48
+
49
+ def build_message(node)
50
+ good_method = build_good_method(node)
51
+ bad_method = build_bad_method(node)
52
+ format(MSG, good_method: good_method, bad_method: bad_method)
53
+ end
54
+
55
+ def build_good_method(node)
56
+ if node.arguments?
57
+ "last(#{node.arguments.first.source}).reverse"
58
+ else
59
+ 'last'
60
+ end
61
+ end
62
+
63
+ def build_bad_method(node)
64
+ if node.arguments?
65
+ "reverse.first(#{node.arguments.first.source})"
66
+ else
67
+ 'reverse.first'
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end