rubocop-performance 1.6.0 → 1.8.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.
- checksums.yaml +5 -5
- data/README.md +1 -1
- data/config/default.yml +77 -10
- data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +39 -4
- data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
- data/lib/rubocop/cop/performance/ancestors_include.rb +48 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +45 -0
- data/lib/rubocop/cop/performance/bind_call.rb +8 -18
- data/lib/rubocop/cop/performance/caller.rb +3 -2
- data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
- data/lib/rubocop/cop/performance/casecmp.rb +12 -20
- data/lib/rubocop/cop/performance/chain_array_allocation.rb +4 -10
- data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
- data/lib/rubocop/cop/performance/compare_with_block.rb +10 -21
- data/lib/rubocop/cop/performance/count.rb +13 -16
- data/lib/rubocop/cop/performance/delete_prefix.rb +43 -28
- data/lib/rubocop/cop/performance/delete_suffix.rb +43 -28
- data/lib/rubocop/cop/performance/detect.rb +63 -31
- data/lib/rubocop/cop/performance/double_start_end_with.rb +16 -24
- data/lib/rubocop/cop/performance/end_with.rb +29 -17
- data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
- data/lib/rubocop/cop/performance/flat_map.rb +20 -22
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +13 -14
- data/lib/rubocop/cop/performance/io_readlines.rb +116 -0
- data/lib/rubocop/cop/performance/open_struct.rb +2 -2
- data/lib/rubocop/cop/performance/range_include.rb +14 -11
- data/lib/rubocop/cop/performance/redundant_block_call.rb +11 -6
- data/lib/rubocop/cop/performance/redundant_match.rb +11 -6
- data/lib/rubocop/cop/performance/redundant_merge.rb +18 -17
- data/lib/rubocop/cop/performance/redundant_sort_block.rb +43 -0
- data/lib/rubocop/cop/performance/redundant_string_chars.rb +133 -0
- data/lib/rubocop/cop/performance/regexp_match.rb +20 -20
- data/lib/rubocop/cop/performance/reverse_each.rb +9 -5
- data/lib/rubocop/cop/performance/reverse_first.rb +72 -0
- data/lib/rubocop/cop/performance/size.rb +41 -43
- data/lib/rubocop/cop/performance/sort_reverse.rb +45 -0
- data/lib/rubocop/cop/performance/squeeze.rb +66 -0
- data/lib/rubocop/cop/performance/start_with.rb +29 -17
- data/lib/rubocop/cop/performance/string_include.rb +55 -0
- data/lib/rubocop/cop/performance/string_replacement.rb +23 -27
- data/lib/rubocop/cop/performance/sum.rb +134 -0
- data/lib/rubocop/cop/performance/times_map.rb +11 -18
- data/lib/rubocop/cop/performance/unfreeze_string.rb +2 -2
- data/lib/rubocop/cop/performance/uri_default_parser.rb +6 -12
- data/lib/rubocop/cop/performance_cops.rb +12 -0
- data/lib/rubocop/performance/version.rb +1 -1
- metadata +33 -8
@@ -72,7 +72,9 @@ module RuboCop
|
|
72
72
|
# do_something($~)
|
73
73
|
# end
|
74
74
|
# end
|
75
|
-
class RegexpMatch <
|
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
|
-
|
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,8 +12,9 @@ module RuboCop
|
|
12
12
|
#
|
13
13
|
# # good
|
14
14
|
# [].reverse_each
|
15
|
-
class ReverseEach <
|
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 = '_'
|
@@ -29,13 +30,16 @@ module RuboCop
|
|
29
30
|
|
30
31
|
range = range_between(location_of_reverse, end_location)
|
31
32
|
|
32
|
-
add_offense(
|
33
|
+
add_offense(range) do |corrector|
|
34
|
+
corrector.replace(replacement_range(node), UNDERSCORE)
|
35
|
+
end
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
+
private
|
40
|
+
|
41
|
+
def replacement_range(node)
|
42
|
+
range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
|
39
43
|
end
|
40
44
|
end
|
41
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 <
|
27
|
-
|
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
|
-
|
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
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
65
|
+
def on_send(node)
|
66
|
+
return if node.parent&.block_type? || !count?(node)
|
71
67
|
|
72
|
-
|
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
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies places where `sort { |a, b| b <=> a }`
|
7
|
+
# can be replaced by a faster `sort.reverse`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# array.sort { |a, b| b <=> a }
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# array.sort.reverse
|
15
|
+
#
|
16
|
+
class SortReverse < Base
|
17
|
+
include SortBlock
|
18
|
+
extend AutoCorrector
|
19
|
+
|
20
|
+
MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
|
21
|
+
|
22
|
+
def on_block(node)
|
23
|
+
sort_with_block?(node) do |send, var_a, var_b, body|
|
24
|
+
replaceable_body?(body, var_b, var_a) do
|
25
|
+
range = sort_range(send, node)
|
26
|
+
|
27
|
+
add_offense(range, message: message(var_a, var_b)) do |corrector|
|
28
|
+
replacement = 'sort.reverse'
|
29
|
+
|
30
|
+
corrector.replace(range, replacement)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def message(var_a, var_b)
|
39
|
+
bad_method = "sort { |#{var_a}, #{var_b}| #{var_b} <=> #{var_a} }"
|
40
|
+
format(MSG, bad_method: bad_method)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies places where `gsub(/a+/, 'a')` and `gsub!(/a+/, 'a')`
|
7
|
+
# can be replaced by `squeeze('a')` and `squeeze!('a')`.
|
8
|
+
#
|
9
|
+
# The `squeeze('a')` method is faster than `gsub(/a+/, 'a')`.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
#
|
13
|
+
# # bad
|
14
|
+
# str.gsub(/a+/, 'a')
|
15
|
+
# str.gsub!(/a+/, 'a')
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# str.squeeze('a')
|
19
|
+
# str.squeeze!('a')
|
20
|
+
#
|
21
|
+
class Squeeze < Base
|
22
|
+
extend AutoCorrector
|
23
|
+
|
24
|
+
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
25
|
+
|
26
|
+
PREFERRED_METHODS = {
|
27
|
+
gsub: :squeeze,
|
28
|
+
gsub!: :squeeze!
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
def_node_matcher :squeeze_candidate?, <<~PATTERN
|
32
|
+
(send
|
33
|
+
$!nil? ${:gsub :gsub!}
|
34
|
+
(regexp
|
35
|
+
(str $#repeating_literal?)
|
36
|
+
(regopt))
|
37
|
+
(str $_))
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
def on_send(node)
|
41
|
+
squeeze_candidate?(node) do |receiver, bad_method, regexp_str, replace_str|
|
42
|
+
regexp_str = regexp_str[0..-2] # delete '+' from the end
|
43
|
+
regexp_str = interpret_string_escapes(regexp_str)
|
44
|
+
return unless replace_str == regexp_str
|
45
|
+
|
46
|
+
good_method = PREFERRED_METHODS[bad_method]
|
47
|
+
message = format(MSG, current: bad_method, prefer: good_method)
|
48
|
+
|
49
|
+
add_offense(node.loc.selector, message: message) do |corrector|
|
50
|
+
string_literal = to_string_literal(replace_str)
|
51
|
+
new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
|
52
|
+
|
53
|
+
corrector.replace(node.source_range, new_code)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def repeating_literal?(regex_str)
|
61
|
+
regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -3,8 +3,11 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Performance
|
6
|
-
# This cop identifies unnecessary use of a regex where
|
7
|
-
#
|
6
|
+
# This cop identifies unnecessary use of a regex where `String#start_with?` would suffice.
|
7
|
+
#
|
8
|
+
# This cop has `SafeMultiline` configuration option that `true` by default because
|
9
|
+
# `^start` is unsafe as it will behave incompatible with `start_with?`
|
10
|
+
# for receiver is multiline string.
|
8
11
|
#
|
9
12
|
# @example
|
10
13
|
# # bad
|
@@ -15,6 +18,12 @@ module RuboCop
|
|
15
18
|
# 'abc'.match(/\Aab/)
|
16
19
|
# /\Aab/.match('abc')
|
17
20
|
#
|
21
|
+
# # good
|
22
|
+
# 'abc'.start_with?('ab')
|
23
|
+
#
|
24
|
+
# @example SafeMultiline: true (default)
|
25
|
+
#
|
26
|
+
# # good
|
18
27
|
# 'abc'.match?(/^ab/)
|
19
28
|
# /^ab/.match?('abc')
|
20
29
|
# 'abc' =~ /^ab/
|
@@ -22,10 +31,19 @@ module RuboCop
|
|
22
31
|
# 'abc'.match(/^ab/)
|
23
32
|
# /^ab/.match('abc')
|
24
33
|
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
|
34
|
+
# @example SafeMultiline: false
|
35
|
+
#
|
36
|
+
# # bad
|
37
|
+
# 'abc'.match?(/^ab/)
|
38
|
+
# /^ab/.match?('abc')
|
39
|
+
# 'abc' =~ /^ab/
|
40
|
+
# /^ab/ =~ 'abc'
|
41
|
+
# 'abc'.match(/^ab/)
|
42
|
+
# /^ab/.match('abc')
|
43
|
+
#
|
44
|
+
class StartWith < Base
|
28
45
|
include RegexpMetacharacter
|
46
|
+
extend AutoCorrector
|
29
47
|
|
30
48
|
MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
|
31
49
|
'the beginning of the string.'
|
@@ -37,25 +55,19 @@ module RuboCop
|
|
37
55
|
PATTERN
|
38
56
|
|
39
57
|
def on_send(node)
|
40
|
-
return unless redundant_regex?(node)
|
58
|
+
return unless (receiver, regex_str = redundant_regex?(node))
|
41
59
|
|
42
|
-
add_offense(node)
|
43
|
-
end
|
44
|
-
alias on_match_with_lvasgn on_send
|
45
|
-
|
46
|
-
def autocorrect(node)
|
47
|
-
redundant_regex?(node) do |receiver, regex_str|
|
60
|
+
add_offense(node) do |corrector|
|
48
61
|
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
|
49
62
|
regex_str = drop_start_metacharacter(regex_str)
|
50
63
|
regex_str = interpret_string_escapes(regex_str)
|
51
64
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
corrector.replace(node.source_range, new_source)
|
56
|
-
end
|
65
|
+
new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})"
|
66
|
+
|
67
|
+
corrector.replace(node.source_range, new_source)
|
57
68
|
end
|
58
69
|
end
|
70
|
+
alias on_match_with_lvasgn on_send
|
59
71
|
end
|
60
72
|
end
|
61
73
|
end
|