rubocop-performance 1.8.1 → 1.10.1

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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/README.md +10 -2
  4. data/config/default.yml +47 -6
  5. data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
  6. data/lib/rubocop/cop/performance/ancestors_include.rb +1 -0
  7. data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +77 -0
  8. data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +1 -0
  9. data/lib/rubocop/cop/performance/bind_call.rb +3 -2
  10. data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +52 -0
  11. data/lib/rubocop/cop/performance/caller.rb +13 -15
  12. data/lib/rubocop/cop/performance/casecmp.rb +1 -0
  13. data/lib/rubocop/cop/performance/chain_array_allocation.rb +20 -18
  14. data/lib/rubocop/cop/performance/constant_regexp.rb +73 -0
  15. data/lib/rubocop/cop/performance/count.rb +1 -0
  16. data/lib/rubocop/cop/performance/delete_prefix.rb +1 -0
  17. data/lib/rubocop/cop/performance/delete_suffix.rb +1 -0
  18. data/lib/rubocop/cop/performance/detect.rb +3 -2
  19. data/lib/rubocop/cop/performance/end_with.rb +1 -0
  20. data/lib/rubocop/cop/performance/fixed_size.rb +1 -0
  21. data/lib/rubocop/cop/performance/flat_map.rb +1 -0
  22. data/lib/rubocop/cop/performance/inefficient_hash_search.rb +2 -0
  23. data/lib/rubocop/cop/performance/io_readlines.rb +3 -7
  24. data/lib/rubocop/cop/performance/method_object_as_block.rb +32 -0
  25. data/lib/rubocop/cop/performance/open_struct.rb +1 -0
  26. data/lib/rubocop/cop/performance/range_include.rb +1 -0
  27. data/lib/rubocop/cop/performance/redundant_block_call.rb +4 -4
  28. data/lib/rubocop/cop/performance/redundant_equality_comparison_block.rb +80 -0
  29. data/lib/rubocop/cop/performance/redundant_match.rb +1 -0
  30. data/lib/rubocop/cop/performance/redundant_merge.rb +1 -0
  31. data/lib/rubocop/cop/performance/redundant_split_regexp_argument.rb +64 -0
  32. data/lib/rubocop/cop/performance/redundant_string_chars.rb +2 -6
  33. data/lib/rubocop/cop/performance/reverse_each.rb +7 -10
  34. data/lib/rubocop/cop/performance/reverse_first.rb +1 -0
  35. data/lib/rubocop/cop/performance/size.rb +1 -0
  36. data/lib/rubocop/cop/performance/squeeze.rb +2 -1
  37. data/lib/rubocop/cop/performance/start_with.rb +1 -0
  38. data/lib/rubocop/cop/performance/string_include.rb +2 -1
  39. data/lib/rubocop/cop/performance/string_replacement.rb +1 -0
  40. data/lib/rubocop/cop/performance/sum.rb +123 -15
  41. data/lib/rubocop/cop/performance/times_map.rb +1 -0
  42. data/lib/rubocop/cop/performance/unfreeze_string.rb +19 -1
  43. data/lib/rubocop/cop/performance/uri_default_parser.rb +1 -0
  44. data/lib/rubocop/cop/performance_cops.rb +6 -0
  45. data/lib/rubocop/performance/version.rb +6 -1
  46. metadata +25 -27
@@ -44,10 +44,10 @@ module RuboCop
44
44
  extend AutoCorrector
45
45
 
46
46
  MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
47
- REPLACEABLE_METHODS = %i[[] slice first last take drop length size empty?].freeze
47
+ RESTRICT_ON_SEND = %i[[] slice first last take drop length size empty?].freeze
48
48
 
49
49
  def_node_matcher :redundant_chars_call?, <<~PATTERN
50
- (send $(send _ :chars) $#replaceable_method? $...)
50
+ (send $(send _ :chars) $_ $...)
51
51
  PATTERN
52
52
 
53
53
  def on_send(node)
@@ -66,10 +66,6 @@ module RuboCop
66
66
 
67
67
  private
68
68
 
69
- def replaceable_method?(method_name)
70
- REPLACEABLE_METHODS.include?(method_name)
71
- end
72
-
73
69
  def offense_range(receiver, node)
74
70
  range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
75
71
  end
@@ -17,29 +17,26 @@ module RuboCop
17
17
  extend AutoCorrector
18
18
 
19
19
  MSG = 'Use `reverse_each` instead of `reverse.each`.'
20
- UNDERSCORE = '_'
20
+ RESTRICT_ON_SEND = %i[each].freeze
21
21
 
22
22
  def_node_matcher :reverse_each?, <<~MATCHER
23
- (send $(send _ :reverse) :each)
23
+ (send (send _ :reverse) :each)
24
24
  MATCHER
25
25
 
26
26
  def on_send(node)
27
- reverse_each?(node) do |receiver|
28
- location_of_reverse = receiver.loc.selector.begin_pos
29
- end_location = node.loc.selector.end_pos
30
-
31
- range = range_between(location_of_reverse, end_location)
27
+ reverse_each?(node) do
28
+ range = offense_range(node)
32
29
 
33
30
  add_offense(range) do |corrector|
34
- corrector.replace(replacement_range(node), UNDERSCORE)
31
+ corrector.replace(range, 'reverse_each')
35
32
  end
36
33
  end
37
34
  end
38
35
 
39
36
  private
40
37
 
41
- def replacement_range(node)
42
- range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
38
+ def offense_range(node)
39
+ range_between(node.children.first.loc.selector.begin_pos, node.loc.selector.end_pos)
43
40
  end
44
41
  end
45
42
  end
@@ -21,6 +21,7 @@ module RuboCop
21
21
  extend AutoCorrector
22
22
 
23
23
  MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
24
+ RESTRICT_ON_SEND = %i[first].freeze
24
25
 
25
26
  def_node_matcher :reverse_first_candidate?, <<~PATTERN
26
27
  (send $(send _ :reverse) :first (int _)?)
@@ -39,6 +39,7 @@ module RuboCop
39
39
  extend AutoCorrector
40
40
 
41
41
  MSG = 'Use `size` instead of `count`.'
42
+ RESTRICT_ON_SEND = %i[count].freeze
42
43
 
43
44
  def_node_matcher :array?, <<~PATTERN
44
45
  {
@@ -22,6 +22,7 @@ module RuboCop
22
22
  extend AutoCorrector
23
23
 
24
24
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
25
+ RESTRICT_ON_SEND = %i[gsub gsub!].freeze
25
26
 
26
27
  PREFERRED_METHODS = {
27
28
  gsub: :squeeze,
@@ -58,7 +59,7 @@ module RuboCop
58
59
  private
59
60
 
60
61
  def repeating_literal?(regex_str)
61
- regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
62
+ regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/o)
62
63
  end
63
64
  end
64
65
  end
@@ -47,6 +47,7 @@ module RuboCop
47
47
 
48
48
  MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
49
49
  'the beginning of the string.'
50
+ RESTRICT_ON_SEND = %i[match =~ match?].freeze
50
51
 
51
52
  def_node_matcher :redundant_regex?, <<~PATTERN
52
53
  {(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt)))
@@ -23,6 +23,7 @@ module RuboCop
23
23
  extend AutoCorrector
24
24
 
25
25
  MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
26
+ RESTRICT_ON_SEND = %i[match =~ match?].freeze
26
27
 
27
28
  def_node_matcher :redundant_regex?, <<~PATTERN
28
29
  {(send $!nil? {:match :=~ :match?} (regexp (str $#literal?) (regopt)))
@@ -47,7 +48,7 @@ module RuboCop
47
48
  private
48
49
 
49
50
  def literal?(regex_str)
50
- regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/)
51
+ regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/o)
51
52
  end
52
53
  end
53
54
  end
@@ -23,6 +23,7 @@ module RuboCop
23
23
  extend AutoCorrector
24
24
 
25
25
  MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
26
+ RESTRICT_ON_SEND = %i[gsub gsub!].freeze
26
27
  DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
27
28
  DELETE = 'delete'
28
29
  TR = 'tr'
@@ -6,28 +6,67 @@ module RuboCop
6
6
  # This cop identifies places where custom code finding the sum of elements
7
7
  # in some Enumerable object can be replaced by `Enumerable#sum` method.
8
8
  #
9
+ # This cop can change auto-correction scope depending on the value of
10
+ # `SafeAutoCorrect`.
11
+ # Its auto-correction is marked as safe by default (`SafeAutoCorrect: true`)
12
+ # to prevent `TypeError` in auto-correced code when initial value is not
13
+ # specified as shown below:
14
+ #
15
+ # [source,ruby]
16
+ # ----
17
+ # ['a', 'b'].sum # => (String can't be coerced into Integer)
18
+ # ----
19
+ #
20
+ # Therefore if initial value is not specified, unsafe auto-corrected will not occur.
21
+ #
22
+ # If you always want to enable auto-correction, you can set `SafeAutoCorrect: false`.
23
+ #
24
+ # [source,yaml]
25
+ # ----
26
+ # Performance/Sum:
27
+ # SafeAutoCorrect: false
28
+ # ----
29
+ #
30
+ # Please note that the auto-correction command line option will be changed from
31
+ # `rubocop -a` to `rubocop -A`, which includes unsafe auto-correction.
32
+ #
9
33
  # @example
10
34
  # # bad
11
- # [1, 2, 3].inject(:+)
35
+ # [1, 2, 3].inject(:+) # These bad cases with no initial value are unsafe and
36
+ # [1, 2, 3].inject(&:+) # will not be auto-correced by default. If you want to
37
+ # [1, 2, 3].reduce { |acc, elem| acc + elem } # auto-corrected, you can set `SafeAutoCorrect: false`.
12
38
  # [1, 2, 3].reduce(10, :+)
13
- # [1, 2, 3].inject(&:+)
14
- # [1, 2, 3].reduce { |acc, elem| acc + elem }
39
+ # [1, 2, 3].map { |elem| elem ** 2 }.sum
40
+ # [1, 2, 3].collect(&:count).sum(10)
15
41
  #
16
42
  # # good
17
43
  # [1, 2, 3].sum
18
44
  # [1, 2, 3].sum(10)
19
- # [1, 2, 3].sum
45
+ # [1, 2, 3].sum { |elem| elem ** 2 }
46
+ # [1, 2, 3].sum(10, &:count)
20
47
  #
21
48
  class Sum < Base
22
49
  include RangeHelp
23
50
  extend AutoCorrector
24
51
 
25
52
  MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
53
+ MSG_IF_NO_INIT_VALUE =
54
+ 'Use `%<good_method>s` instead of `%<bad_method>s`, unless calling `%<bad_method>s` on an empty array.'
55
+ RESTRICT_ON_SEND = %i[inject reduce sum].freeze
26
56
 
27
57
  def_node_matcher :sum_candidate?, <<~PATTERN
28
58
  (send _ ${:inject :reduce} $_init ? ${(sym :+) (block_pass (sym :+))})
29
59
  PATTERN
30
60
 
61
+ def_node_matcher :sum_map_candidate?, <<~PATTERN
62
+ (send
63
+ {
64
+ (block $(send _ {:map :collect}) ...)
65
+ $(send _ {:map :collect} (block_pass _))
66
+ }
67
+ :sum $_init ?)
68
+ PATTERN
69
+
31
70
  def_node_matcher :sum_with_block_candidate?, <<~PATTERN
32
71
  (block
33
72
  $(send _ {:inject :reduce} $_init ?)
@@ -41,14 +80,10 @@ module RuboCop
41
80
  alias elem_plus_acc? acc_plus_elem?
42
81
 
43
82
  def on_send(node)
44
- sum_candidate?(node) do |method, init, operation|
45
- range = sum_method_range(node)
46
- message = build_method_message(method, init, operation)
83
+ return if empty_array_literal?(node)
47
84
 
48
- add_offense(range, message: message) do |corrector|
49
- autocorrect(corrector, init, range)
50
- end
51
- end
85
+ handle_sum_candidate(node)
86
+ handle_sum_map_candidate(node)
52
87
  end
53
88
 
54
89
  def on_block(node)
@@ -66,25 +101,87 @@ module RuboCop
66
101
 
67
102
  private
68
103
 
104
+ def handle_sum_candidate(node)
105
+ sum_candidate?(node) do |method, init, operation|
106
+ range = sum_method_range(node)
107
+ message = build_method_message(node, method, init, operation)
108
+
109
+ add_offense(range, message: message) do |corrector|
110
+ autocorrect(corrector, init, range)
111
+ end
112
+ end
113
+ end
114
+
115
+ def handle_sum_map_candidate(node)
116
+ sum_map_candidate?(node) do |map, init|
117
+ next if node.block_literal? || node.block_argument?
118
+
119
+ message = build_sum_map_message(map.method_name, init)
120
+
121
+ add_offense(sum_map_range(map, node), message: message) do |corrector|
122
+ autocorrect_sum_map(corrector, node, map, init)
123
+ end
124
+ end
125
+ end
126
+
127
+ def empty_array_literal?(node)
128
+ receiver = node.children.first
129
+ array_literal?(node) && receiver && receiver.children.empty?
130
+ end
131
+
132
+ def array_literal?(node)
133
+ receiver = node.children.first
134
+ receiver&.literal? && receiver&.array_type?
135
+ end
136
+
69
137
  def autocorrect(corrector, init, range)
70
- return if init.empty?
138
+ return if init.empty? && safe_autocorrect?
71
139
 
72
140
  replacement = build_good_method(init)
73
141
 
74
142
  corrector.replace(range, replacement)
75
143
  end
76
144
 
145
+ def autocorrect_sum_map(corrector, sum, map, init)
146
+ sum_range = method_call_with_args_range(sum)
147
+ map_range = method_call_with_args_range(map)
148
+
149
+ block_pass = map.last_argument if map.last_argument&.block_pass_type?
150
+ replacement = build_good_method(init, block_pass)
151
+
152
+ corrector.remove(sum_range)
153
+
154
+ dot = '.' if map.receiver
155
+ corrector.replace(map_range, "#{dot}#{replacement}")
156
+ end
157
+
77
158
  def sum_method_range(node)
78
159
  range_between(node.loc.selector.begin_pos, node.loc.end.end_pos)
79
160
  end
80
161
 
162
+ def sum_map_range(map, sum)
163
+ range_between(map.loc.selector.begin_pos, sum.source_range.end.end_pos)
164
+ end
165
+
81
166
  def sum_block_range(send, node)
82
167
  range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
83
168
  end
84
169
 
85
- def build_method_message(method, init, operation)
170
+ def build_method_message(node, method, init, operation)
86
171
  good_method = build_good_method(init)
87
172
  bad_method = build_method_bad_method(init, method, operation)
173
+ msg = if init.empty? && !array_literal?(node)
174
+ MSG_IF_NO_INIT_VALUE
175
+ else
176
+ MSG
177
+ end
178
+ format(msg, good_method: good_method, bad_method: bad_method)
179
+ end
180
+
181
+ def build_sum_map_message(method, init)
182
+ sum_method = build_good_method(init)
183
+ good_method = "#{sum_method} { ... }"
184
+ bad_method = "#{method} { ... }.#{sum_method}"
88
185
  format(MSG, good_method: good_method, bad_method: bad_method)
89
186
  end
90
187
 
@@ -94,13 +191,16 @@ module RuboCop
94
191
  format(MSG, good_method: good_method, bad_method: bad_method)
95
192
  end
96
193
 
97
- def build_good_method(init)
194
+ def build_good_method(init, block_pass = nil)
98
195
  good_method = 'sum'
99
196
 
197
+ args = []
100
198
  unless init.empty?
101
199
  init = init.first
102
- good_method += "(#{init.source})" unless init.int_type? && init.value.zero?
200
+ args << init.source unless init.int_type? && init.value.zero?
103
201
  end
202
+ args << block_pass.source if block_pass
203
+ good_method += "(#{args.join(', ')})" unless args.empty?
104
204
  good_method
105
205
  end
106
206
 
@@ -128,6 +228,14 @@ module RuboCop
128
228
  bad_method += " { |#{var_acc}, #{var_elem}| #{body.source} }"
129
229
  bad_method
130
230
  end
231
+
232
+ def method_call_with_args_range(node)
233
+ if (receiver = node.receiver)
234
+ receiver.source_range.end.join(node.source_range.end)
235
+ else
236
+ node.source_range
237
+ end
238
+ end
131
239
  end
132
240
  end
133
241
  end
@@ -23,6 +23,7 @@ module RuboCop
23
23
  MESSAGE = 'Use `Array.new(%<count>s)` with a block ' \
24
24
  'instead of `.times.%<map_or_collect>s`'
25
25
  MESSAGE_ONLY_IF = 'only if `%<count>s` is always 0 or more'
26
+ RESTRICT_ON_SEND = %i[map collect].freeze
26
27
 
27
28
  def on_send(node)
28
29
  check(node)
@@ -10,6 +10,7 @@ module RuboCop
10
10
  # NOTE: `String.new` (without operator) is not exactly the same as `+''`.
11
11
  # These differ in encoding. `String.new.encoding` is always `ASCII-8BIT`.
12
12
  # However, `(+'').encoding` is the same as script encoding(e.g. `UTF-8`).
13
+ # Therefore, auto-correction is unsafe.
13
14
  # So, if you expect `ASCII-8BIT` encoding, disable this cop.
14
15
  #
15
16
  # @example
@@ -24,7 +25,10 @@ module RuboCop
24
25
  # +'something'
25
26
  # +''
26
27
  class UnfreezeString < Base
28
+ extend AutoCorrector
29
+
27
30
  MSG = 'Use unary plus to get an unfrozen string literal.'
31
+ RESTRICT_ON_SEND = %i[dup new].freeze
28
32
 
29
33
  def_node_matcher :dup_string?, <<~PATTERN
30
34
  (send {str dstr} :dup)
@@ -38,7 +42,21 @@ module RuboCop
38
42
  PATTERN
39
43
 
40
44
  def on_send(node)
41
- add_offense(node) if dup_string?(node) || string_new?(node)
45
+ return unless dup_string?(node) || string_new?(node)
46
+
47
+ add_offense(node) do |corrector|
48
+ corrector.replace(node, "+#{string_value(node)}")
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ def string_value(node)
55
+ if node.receiver.source == 'String' && node.method?(:new)
56
+ node.arguments.empty? ? "''" : node.first_argument.source
57
+ else
58
+ node.receiver.source
59
+ end
42
60
  end
43
61
  end
44
62
  end
@@ -18,6 +18,7 @@ module RuboCop
18
18
 
19
19
  MSG = 'Use `%<double_colon>sURI::DEFAULT_PARSER` instead of ' \
20
20
  '`%<double_colon>sURI::Parser.new`.'
21
+ RESTRICT_ON_SEND = %i[new].freeze
21
22
 
22
23
  def_node_matcher :uri_parser_new?, <<~PATTERN
23
24
  (send
@@ -4,13 +4,16 @@ require_relative 'mixin/regexp_metacharacter'
4
4
  require_relative 'mixin/sort_block'
5
5
 
6
6
  require_relative 'performance/ancestors_include'
7
+ require_relative 'performance/array_semi_infinite_range_slice'
7
8
  require_relative 'performance/big_decimal_with_numeric_argument'
8
9
  require_relative 'performance/bind_call'
10
+ require_relative 'performance/block_given_with_explicit_block'
9
11
  require_relative 'performance/caller'
10
12
  require_relative 'performance/case_when_splat'
11
13
  require_relative 'performance/casecmp'
12
14
  require_relative 'performance/collection_literal_in_loop'
13
15
  require_relative 'performance/compare_with_block'
16
+ require_relative 'performance/constant_regexp'
14
17
  require_relative 'performance/count'
15
18
  require_relative 'performance/delete_prefix'
16
19
  require_relative 'performance/delete_suffix'
@@ -20,13 +23,16 @@ require_relative 'performance/end_with'
20
23
  require_relative 'performance/fixed_size'
21
24
  require_relative 'performance/flat_map'
22
25
  require_relative 'performance/inefficient_hash_search'
26
+ require_relative 'performance/method_object_as_block'
23
27
  require_relative 'performance/open_struct'
24
28
  require_relative 'performance/range_include'
25
29
  require_relative 'performance/io_readlines'
26
30
  require_relative 'performance/redundant_block_call'
31
+ require_relative 'performance/redundant_equality_comparison_block'
27
32
  require_relative 'performance/redundant_match'
28
33
  require_relative 'performance/redundant_merge'
29
34
  require_relative 'performance/redundant_sort_block'
35
+ require_relative 'performance/redundant_split_regexp_argument'
30
36
  require_relative 'performance/redundant_string_chars'
31
37
  require_relative 'performance/regexp_match'
32
38
  require_relative 'performance/reverse_each'
@@ -2,8 +2,13 @@
2
2
 
3
3
  module RuboCop
4
4
  module Performance
5
+ # This module holds the RuboCop Performance version information.
5
6
  module Version
6
- STRING = '1.8.1'
7
+ STRING = '1.10.1'
8
+
9
+ def self.document_version
10
+ STRING.match('\d+\.\d+').to_s
11
+ end
7
12
  end
8
13
  end
9
14
  end