rubocop-performance 1.6.1 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
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