rubocop-performance 1.8.0 → 1.10.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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +10 -2
- data/config/default.yml +47 -6
- data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +4 -4
- data/lib/rubocop/cop/performance/ancestors_include.rb +1 -0
- data/lib/rubocop/cop/performance/array_semi_infinite_range_slice.rb +77 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +1 -0
- data/lib/rubocop/cop/performance/bind_call.rb +3 -2
- data/lib/rubocop/cop/performance/block_given_with_explicit_block.rb +52 -0
- data/lib/rubocop/cop/performance/caller.rb +13 -15
- data/lib/rubocop/cop/performance/casecmp.rb +1 -0
- data/lib/rubocop/cop/performance/chain_array_allocation.rb +20 -18
- data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +1 -1
- data/lib/rubocop/cop/performance/constant_regexp.rb +73 -0
- data/lib/rubocop/cop/performance/count.rb +1 -0
- data/lib/rubocop/cop/performance/delete_prefix.rb +1 -0
- data/lib/rubocop/cop/performance/delete_suffix.rb +1 -0
- data/lib/rubocop/cop/performance/detect.rb +47 -17
- data/lib/rubocop/cop/performance/end_with.rb +1 -0
- data/lib/rubocop/cop/performance/fixed_size.rb +1 -0
- data/lib/rubocop/cop/performance/flat_map.rb +1 -0
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +2 -0
- data/lib/rubocop/cop/performance/io_readlines.rb +3 -7
- data/lib/rubocop/cop/performance/method_object_as_block.rb +32 -0
- data/lib/rubocop/cop/performance/open_struct.rb +1 -0
- data/lib/rubocop/cop/performance/range_include.rb +1 -0
- data/lib/rubocop/cop/performance/redundant_block_call.rb +4 -4
- data/lib/rubocop/cop/performance/redundant_equality_comparison_block.rb +72 -0
- data/lib/rubocop/cop/performance/redundant_match.rb +1 -0
- data/lib/rubocop/cop/performance/redundant_merge.rb +1 -0
- data/lib/rubocop/cop/performance/redundant_split_regexp_argument.rb +67 -0
- data/lib/rubocop/cop/performance/redundant_string_chars.rb +2 -6
- data/lib/rubocop/cop/performance/reverse_each.rb +7 -10
- data/lib/rubocop/cop/performance/reverse_first.rb +1 -0
- data/lib/rubocop/cop/performance/size.rb +1 -0
- data/lib/rubocop/cop/performance/squeeze.rb +2 -1
- data/lib/rubocop/cop/performance/start_with.rb +1 -0
- data/lib/rubocop/cop/performance/string_include.rb +2 -1
- data/lib/rubocop/cop/performance/string_replacement.rb +1 -0
- data/lib/rubocop/cop/performance/sum.rb +131 -18
- data/lib/rubocop/cop/performance/times_map.rb +1 -0
- data/lib/rubocop/cop/performance/unfreeze_string.rb +19 -1
- data/lib/rubocop/cop/performance/uri_default_parser.rb +1 -0
- data/lib/rubocop/cop/performance_cops.rb +6 -0
- data/lib/rubocop/performance/version.rb +6 -1
- metadata +29 -17
@@ -22,6 +22,7 @@ module RuboCop
|
|
22
22
|
|
23
23
|
MSG = 'Use `=~` in places where the `MatchData` returned by ' \
|
24
24
|
'`#match` will not be used.'
|
25
|
+
RESTRICT_ON_SEND = %i[match].freeze
|
25
26
|
|
26
27
|
# 'match' is a fairly generic name, so we don't flag it unless we see
|
27
28
|
# a string or regexp literal on one side or the other
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies places where `split` argument can be replaced from
|
7
|
+
# a deterministic regexp to a string.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# 'a,b,c'.split(/,/)
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# 'a,b,c'.split(',')
|
15
|
+
class RedundantSplitRegexpArgument < Base
|
16
|
+
extend AutoCorrector
|
17
|
+
|
18
|
+
MSG = 'Use string as argument instead of regexp.'
|
19
|
+
RESTRICT_ON_SEND = %i[split].freeze
|
20
|
+
DETERMINISTIC_REGEX = /\A(?:#{LITERAL_REGEX})+\Z/.freeze
|
21
|
+
STR_SPECIAL_CHARS = %w[\n \" \' \\\\ \t \b \f \r].freeze
|
22
|
+
|
23
|
+
def_node_matcher :split_call_with_regexp?, <<~PATTERN
|
24
|
+
{(send !nil? :split {regexp})}
|
25
|
+
PATTERN
|
26
|
+
|
27
|
+
def on_send(node)
|
28
|
+
return unless split_call_with_regexp?(node)
|
29
|
+
return unless determinist_regexp?(node.first_argument)
|
30
|
+
|
31
|
+
add_offense(node.first_argument) do |corrector|
|
32
|
+
autocorrect(corrector, node)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def determinist_regexp?(first_argument)
|
39
|
+
DETERMINISTIC_REGEX.match?(first_argument.source)
|
40
|
+
end
|
41
|
+
|
42
|
+
def autocorrect(corrector, node)
|
43
|
+
new_argument = replacement(node)
|
44
|
+
|
45
|
+
corrector.replace(node.first_argument, "\"#{new_argument}\"")
|
46
|
+
end
|
47
|
+
|
48
|
+
def replacement(node)
|
49
|
+
regexp_content = node.first_argument.content
|
50
|
+
stack = []
|
51
|
+
chars = regexp_content.chars.each_with_object([]) do |char, strings|
|
52
|
+
if stack.empty? && char == '\\'
|
53
|
+
stack.push(char)
|
54
|
+
else
|
55
|
+
strings << "#{stack.pop}#{char}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
chars.map do |char|
|
59
|
+
char = char.dup
|
60
|
+
char.delete!('\\') unless STR_SPECIAL_CHARS.include?(char)
|
61
|
+
char
|
62
|
+
end.join
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -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
|
-
|
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)
|
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
|
-
|
20
|
+
RESTRICT_ON_SEND = %i[each].freeze
|
21
21
|
|
22
22
|
def_node_matcher :reverse_each?, <<~MATCHER
|
23
|
-
(send
|
23
|
+
(send (send _ :reverse) :each)
|
24
24
|
MATCHER
|
25
25
|
|
26
26
|
def on_send(node)
|
27
|
-
reverse_each?(node) do
|
28
|
-
|
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(
|
31
|
+
corrector.replace(range, 'reverse_each')
|
35
32
|
end
|
36
33
|
end
|
37
34
|
end
|
38
35
|
|
39
36
|
private
|
40
37
|
|
41
|
-
def
|
42
|
-
range_between(node.loc.
|
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
|
@@ -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
|
@@ -6,25 +6,65 @@ 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].
|
39
|
+
# [1, 2, 3].map { |elem| elem ** 2 }.sum
|
40
|
+
# [1, 2, 3].collect(&:count).sum(10)
|
14
41
|
#
|
15
42
|
# # good
|
16
43
|
# [1, 2, 3].sum
|
17
44
|
# [1, 2, 3].sum(10)
|
18
|
-
# [1, 2, 3].sum
|
45
|
+
# [1, 2, 3].sum { |elem| elem ** 2 }
|
46
|
+
# [1, 2, 3].sum(10, &:count)
|
19
47
|
#
|
20
48
|
class Sum < Base
|
21
49
|
include RangeHelp
|
22
50
|
extend AutoCorrector
|
23
51
|
|
24
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
|
25
56
|
|
26
57
|
def_node_matcher :sum_candidate?, <<~PATTERN
|
27
|
-
(send _ ${:inject :reduce} $_init ? (sym :+))
|
58
|
+
(send _ ${:inject :reduce} $_init ? ${(sym :+) (block_pass (sym :+))})
|
59
|
+
PATTERN
|
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 ?)
|
28
68
|
PATTERN
|
29
69
|
|
30
70
|
def_node_matcher :sum_with_block_candidate?, <<~PATTERN
|
@@ -40,14 +80,10 @@ module RuboCop
|
|
40
80
|
alias elem_plus_acc? acc_plus_elem?
|
41
81
|
|
42
82
|
def on_send(node)
|
43
|
-
|
44
|
-
range = sum_method_range(node)
|
45
|
-
message = build_method_message(method, init)
|
83
|
+
return if empty_array_literal?(node)
|
46
84
|
|
47
|
-
|
48
|
-
|
49
|
-
end
|
50
|
-
end
|
85
|
+
handle_sum_candidate(node)
|
86
|
+
handle_sum_map_candidate(node)
|
51
87
|
end
|
52
88
|
|
53
89
|
def on_block(node)
|
@@ -65,25 +101,87 @@ module RuboCop
|
|
65
101
|
|
66
102
|
private
|
67
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
|
+
|
68
137
|
def autocorrect(corrector, init, range)
|
69
|
-
return if init.empty?
|
138
|
+
return if init.empty? && safe_autocorrect?
|
70
139
|
|
71
140
|
replacement = build_good_method(init)
|
72
141
|
|
73
142
|
corrector.replace(range, replacement)
|
74
143
|
end
|
75
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
|
+
|
76
158
|
def sum_method_range(node)
|
77
159
|
range_between(node.loc.selector.begin_pos, node.loc.end.end_pos)
|
78
160
|
end
|
79
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
|
+
|
80
166
|
def sum_block_range(send, node)
|
81
167
|
range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
|
82
168
|
end
|
83
169
|
|
84
|
-
def build_method_message(method, init)
|
170
|
+
def build_method_message(node, method, init, operation)
|
85
171
|
good_method = build_good_method(init)
|
86
|
-
bad_method = build_method_bad_method(init, method)
|
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}"
|
87
185
|
format(MSG, good_method: good_method, bad_method: bad_method)
|
88
186
|
end
|
89
187
|
|
@@ -93,23 +191,30 @@ module RuboCop
|
|
93
191
|
format(MSG, good_method: good_method, bad_method: bad_method)
|
94
192
|
end
|
95
193
|
|
96
|
-
def build_good_method(init)
|
194
|
+
def build_good_method(init, block_pass = nil)
|
97
195
|
good_method = 'sum'
|
98
196
|
|
197
|
+
args = []
|
99
198
|
unless init.empty?
|
100
199
|
init = init.first
|
101
|
-
|
200
|
+
args << init.source unless init.int_type? && init.value.zero?
|
102
201
|
end
|
202
|
+
args << block_pass.source if block_pass
|
203
|
+
good_method += "(#{args.join(', ')})" unless args.empty?
|
103
204
|
good_method
|
104
205
|
end
|
105
206
|
|
106
|
-
def build_method_bad_method(init, method)
|
207
|
+
def build_method_bad_method(init, method, operation)
|
107
208
|
bad_method = "#{method}("
|
108
209
|
unless init.empty?
|
109
210
|
init = init.first
|
110
211
|
bad_method += "#{init.source}, "
|
111
212
|
end
|
112
|
-
bad_method +=
|
213
|
+
bad_method += if operation.block_pass_type?
|
214
|
+
'&:+)'
|
215
|
+
else
|
216
|
+
':+)'
|
217
|
+
end
|
113
218
|
bad_method
|
114
219
|
end
|
115
220
|
|
@@ -123,6 +228,14 @@ module RuboCop
|
|
123
228
|
bad_method += " { |#{var_acc}, #{var_elem}| #{body.source} }"
|
124
229
|
bad_method
|
125
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
|
126
239
|
end
|
127
240
|
end
|
128
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)
|