rubocop-performance 1.5.0 → 1.7.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 +5 -1
- data/config/default.yml +75 -6
- data/lib/rubocop/cop/mixin/regexp_metacharacter.rb +76 -0
- data/lib/rubocop/cop/mixin/sort_block.rb +28 -0
- data/lib/rubocop/cop/performance/ancestors_include.rb +45 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +43 -0
- data/lib/rubocop/cop/performance/bind_call.rb +87 -0
- data/lib/rubocop/cop/performance/caller.rb +3 -3
- data/lib/rubocop/cop/performance/casecmp.rb +5 -3
- data/lib/rubocop/cop/performance/chain_array_allocation.rb +1 -1
- data/lib/rubocop/cop/performance/compare_with_block.rb +2 -2
- data/lib/rubocop/cop/performance/count.rb +3 -3
- data/lib/rubocop/cop/performance/delete_prefix.rb +96 -0
- data/lib/rubocop/cop/performance/delete_suffix.rb +96 -0
- data/lib/rubocop/cop/performance/detect.rb +1 -1
- data/lib/rubocop/cop/performance/double_start_end_with.rb +2 -2
- data/lib/rubocop/cop/performance/end_with.rb +36 -13
- data/lib/rubocop/cop/performance/fixed_size.rb +1 -1
- data/lib/rubocop/cop/performance/flat_map.rb +1 -1
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +1 -1
- data/lib/rubocop/cop/performance/io_readlines.rb +127 -0
- data/lib/rubocop/cop/performance/open_struct.rb +1 -1
- data/lib/rubocop/cop/performance/range_include.rb +10 -8
- data/lib/rubocop/cop/performance/redundant_block_call.rb +3 -3
- data/lib/rubocop/cop/performance/redundant_match.rb +2 -2
- data/lib/rubocop/cop/performance/redundant_merge.rb +21 -8
- data/lib/rubocop/cop/performance/redundant_sort_block.rb +53 -0
- data/lib/rubocop/cop/performance/redundant_string_chars.rb +137 -0
- data/lib/rubocop/cop/performance/regexp_match.rb +13 -13
- data/lib/rubocop/cop/performance/reverse_each.rb +3 -2
- data/lib/rubocop/cop/performance/reverse_first.rb +78 -0
- data/lib/rubocop/cop/performance/size.rb +35 -37
- data/lib/rubocop/cop/performance/sort_reverse.rb +54 -0
- data/lib/rubocop/cop/performance/squeeze.rb +70 -0
- data/lib/rubocop/cop/performance/start_with.rb +36 -16
- data/lib/rubocop/cop/performance/string_include.rb +57 -0
- data/lib/rubocop/cop/performance/string_replacement.rb +4 -11
- data/lib/rubocop/cop/performance/times_map.rb +1 -1
- data/lib/rubocop/cop/performance/unfreeze_string.rb +3 -7
- data/lib/rubocop/cop/performance/uri_default_parser.rb +1 -1
- data/lib/rubocop/cop/performance_cops.rb +15 -0
- data/lib/rubocop/performance/inject.rb +1 -1
- data/lib/rubocop/performance/version.rb +1 -1
- metadata +25 -11
@@ -0,0 +1,78 @@
|
|
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 < Cop
|
20
|
+
include RangeHelp
|
21
|
+
|
22
|
+
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
|
23
|
+
|
24
|
+
def_node_matcher :reverse_first_candidate?, <<~PATTERN
|
25
|
+
(send $(send _ :reverse) :first (int _)?)
|
26
|
+
PATTERN
|
27
|
+
|
28
|
+
def on_send(node)
|
29
|
+
reverse_first_candidate?(node) do |receiver|
|
30
|
+
range = correction_range(receiver, node)
|
31
|
+
message = build_message(node)
|
32
|
+
|
33
|
+
add_offense(node, location: range, message: message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def autocorrect(node)
|
38
|
+
reverse_first_candidate?(node) do |receiver|
|
39
|
+
range = correction_range(receiver, node)
|
40
|
+
replacement = build_good_method(node)
|
41
|
+
|
42
|
+
lambda do |corrector|
|
43
|
+
corrector.replace(range, replacement)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def correction_range(receiver, node)
|
51
|
+
range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_message(node)
|
55
|
+
good_method = build_good_method(node)
|
56
|
+
bad_method = build_bad_method(node)
|
57
|
+
format(MSG, good_method: good_method, bad_method: bad_method)
|
58
|
+
end
|
59
|
+
|
60
|
+
def build_good_method(node)
|
61
|
+
if node.arguments?
|
62
|
+
"last(#{node.arguments.first.source}).reverse"
|
63
|
+
else
|
64
|
+
'last'
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def build_bad_method(node)
|
69
|
+
if node.arguments?
|
70
|
+
"reverse.first(#{node.arguments.first.source})"
|
71
|
+
else
|
72
|
+
'reverse.first'
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -9,15 +9,27 @@ 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 }
|
@@ -26,8 +38,30 @@ module RuboCop
|
|
26
38
|
class Size < Cop
|
27
39
|
MSG = 'Use `size` instead of `count`.'
|
28
40
|
|
41
|
+
def_node_matcher :array?, <<~PATTERN
|
42
|
+
{
|
43
|
+
[!nil? array_type?]
|
44
|
+
(send _ :to_a)
|
45
|
+
(send (const nil? :Array) :[] _)
|
46
|
+
(send nil? :Array _)
|
47
|
+
}
|
48
|
+
PATTERN
|
49
|
+
|
50
|
+
def_node_matcher :hash?, <<~PATTERN
|
51
|
+
{
|
52
|
+
[!nil? hash_type?]
|
53
|
+
(send _ :to_h)
|
54
|
+
(send (const nil? :Hash) :[] _)
|
55
|
+
(send nil? :Hash _)
|
56
|
+
}
|
57
|
+
PATTERN
|
58
|
+
|
59
|
+
def_node_matcher :count?, <<~PATTERN
|
60
|
+
(send {#array? #hash?} :count)
|
61
|
+
PATTERN
|
62
|
+
|
29
63
|
def on_send(node)
|
30
|
-
return
|
64
|
+
return if node.parent&.block_type? || !count?(node)
|
31
65
|
|
32
66
|
add_offense(node, location: :selector)
|
33
67
|
end
|
@@ -35,42 +69,6 @@ module RuboCop
|
|
35
69
|
def autocorrect(node)
|
36
70
|
->(corrector) { corrector.replace(node.loc.selector, 'size') }
|
37
71
|
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
|
46
|
-
|
47
|
-
def eligible_receiver?(node)
|
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_name == :to_a
|
64
|
-
end
|
65
|
-
|
66
|
-
def hash?(node)
|
67
|
-
return true if node.hash_type?
|
68
|
-
return false unless node.send_type?
|
69
|
-
|
70
|
-
_, constant = *node.receiver
|
71
|
-
|
72
|
-
constant == :Hash || node.method_name == :to_h
|
73
|
-
end
|
74
72
|
end
|
75
73
|
end
|
76
74
|
end
|
@@ -0,0 +1,54 @@
|
|
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 < Cop
|
17
|
+
include SortBlock
|
18
|
+
|
19
|
+
MSG = 'Use `sort.reverse` instead of `%<bad_method>s`.'
|
20
|
+
|
21
|
+
def on_block(node)
|
22
|
+
sort_with_block?(node) do |send, var_a, var_b, body|
|
23
|
+
replaceable_body?(body, var_b, var_a) do
|
24
|
+
range = sort_range(send, node)
|
25
|
+
|
26
|
+
add_offense(
|
27
|
+
node,
|
28
|
+
location: range,
|
29
|
+
message: message(var_a, var_b)
|
30
|
+
)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def autocorrect(node)
|
36
|
+
sort_with_block?(node) do |send, _var_a, _var_b, _body|
|
37
|
+
lambda do |corrector|
|
38
|
+
range = sort_range(send, node)
|
39
|
+
replacement = 'sort.reverse'
|
40
|
+
corrector.replace(range, replacement)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def message(var_a, var_b)
|
48
|
+
bad_method = "sort { |#{var_a}, #{var_b}| #{var_b} <=> #{var_a} }"
|
49
|
+
format(MSG, bad_method: bad_method)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,70 @@
|
|
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 < Cop
|
22
|
+
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
23
|
+
|
24
|
+
PREFERRED_METHODS = {
|
25
|
+
gsub: :squeeze,
|
26
|
+
gsub!: :squeeze!
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
def_node_matcher :squeeze_candidate?, <<~PATTERN
|
30
|
+
(send
|
31
|
+
$!nil? ${:gsub :gsub!}
|
32
|
+
(regexp
|
33
|
+
(str $#repeating_literal?)
|
34
|
+
(regopt))
|
35
|
+
(str $_))
|
36
|
+
PATTERN
|
37
|
+
|
38
|
+
def on_send(node)
|
39
|
+
squeeze_candidate?(node) do |_, bad_method, regexp_str, replace_str|
|
40
|
+
regexp_str = regexp_str[0..-2] # delete '+' from the end
|
41
|
+
regexp_str = interpret_string_escapes(regexp_str)
|
42
|
+
return unless replace_str == regexp_str
|
43
|
+
|
44
|
+
good_method = PREFERRED_METHODS[bad_method]
|
45
|
+
message = format(MSG, current: bad_method, prefer: good_method)
|
46
|
+
add_offense(node, location: :selector, message: message)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def autocorrect(node)
|
51
|
+
squeeze_candidate?(node) do |receiver, bad_method, _regexp_str, replace_str|
|
52
|
+
lambda do |corrector|
|
53
|
+
good_method = PREFERRED_METHODS[bad_method]
|
54
|
+
string_literal = to_string_literal(replace_str)
|
55
|
+
|
56
|
+
new_code = "#{receiver.source}.#{good_method}(#{string_literal})"
|
57
|
+
corrector.replace(node.source_range, new_code)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def repeating_literal?(regex_str)
|
65
|
+
regex_str.match?(/\A(?:#{Util::LITERAL_REGEX})\+\z/)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -3,47 +3,67 @@
|
|
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
|
11
14
|
# 'abc'.match?(/\Aab/)
|
15
|
+
# /\Aab/.match?('abc')
|
12
16
|
# 'abc' =~ /\Aab/
|
17
|
+
# /\Aab/ =~ 'abc'
|
13
18
|
# 'abc'.match(/\Aab/)
|
19
|
+
# /\Aab/.match('abc')
|
14
20
|
#
|
15
21
|
# # good
|
16
22
|
# 'abc'.start_with?('ab')
|
23
|
+
#
|
24
|
+
# @example SafeMultiline: true (default)
|
25
|
+
#
|
26
|
+
# # good
|
27
|
+
# 'abc'.match?(/^ab/)
|
28
|
+
# /^ab/.match?('abc')
|
29
|
+
# 'abc' =~ /^ab/
|
30
|
+
# /^ab/ =~ 'abc'
|
31
|
+
# 'abc'.match(/^ab/)
|
32
|
+
# /^ab/.match('abc')
|
33
|
+
#
|
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
|
+
#
|
17
44
|
class StartWith < Cop
|
45
|
+
include RegexpMetacharacter
|
46
|
+
|
18
47
|
MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
|
19
48
|
'the beginning of the string.'
|
20
|
-
SINGLE_QUOTE = "'"
|
21
49
|
|
22
|
-
def_node_matcher :redundant_regex?,
|
50
|
+
def_node_matcher :redundant_regex?, <<~PATTERN
|
23
51
|
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt)))
|
24
|
-
(send (regexp (str $#literal_at_start?) (regopt)) {:match
|
52
|
+
(send (regexp (str $#literal_at_start?) (regopt)) {:match :match?} $_)
|
53
|
+
(match-with-lvasgn (regexp (str $#literal_at_start?) (regopt)) $_)}
|
25
54
|
PATTERN
|
26
55
|
|
27
|
-
def literal_at_start?(regex_str)
|
28
|
-
# is this regexp 'literal' in the sense of only matching literal
|
29
|
-
# chars, rather than using metachars like `.` and `*` and so on?
|
30
|
-
# also, is it anchored at the start of the string?
|
31
|
-
# (tricky: \s, \d, and so on are metacharacters, but other characters
|
32
|
-
# escaped with a slash are just literals. LITERAL_REGEX takes all
|
33
|
-
# that into account.)
|
34
|
-
regex_str =~ /\A\\A(?:#{LITERAL_REGEX})+\z/
|
35
|
-
end
|
36
|
-
|
37
56
|
def on_send(node)
|
38
57
|
return unless redundant_regex?(node)
|
39
58
|
|
40
59
|
add_offense(node)
|
41
60
|
end
|
61
|
+
alias on_match_with_lvasgn on_send
|
42
62
|
|
43
63
|
def autocorrect(node)
|
44
64
|
redundant_regex?(node) do |receiver, regex_str|
|
45
65
|
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
|
46
|
-
regex_str = regex_str
|
66
|
+
regex_str = drop_start_metacharacter(regex_str)
|
47
67
|
regex_str = interpret_string_escapes(regex_str)
|
48
68
|
|
49
69
|
lambda do |corrector|
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies unnecessary use of a regex where
|
7
|
+
# `String#include?` would suffice.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# 'abc'.match?(/ab/)
|
12
|
+
# /ab/.match?('abc')
|
13
|
+
# 'abc' =~ /ab/
|
14
|
+
# /ab/ =~ 'abc'
|
15
|
+
# 'abc'.match(/ab/)
|
16
|
+
# /ab/.match('abc')
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# 'abc'.include?('ab')
|
20
|
+
class StringInclude < Cop
|
21
|
+
MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
|
22
|
+
|
23
|
+
def_node_matcher :redundant_regex?, <<~PATTERN
|
24
|
+
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal?) (regopt)))
|
25
|
+
(send (regexp (str $#literal?) (regopt)) {:match :match?} $str)
|
26
|
+
(match-with-lvasgn (regexp (str $#literal?) (regopt)) $_)}
|
27
|
+
PATTERN
|
28
|
+
|
29
|
+
def on_send(node)
|
30
|
+
return unless redundant_regex?(node)
|
31
|
+
|
32
|
+
add_offense(node)
|
33
|
+
end
|
34
|
+
alias on_match_with_lvasgn on_send
|
35
|
+
|
36
|
+
def autocorrect(node)
|
37
|
+
redundant_regex?(node) do |receiver, regex_str|
|
38
|
+
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
|
39
|
+
regex_str = interpret_string_escapes(regex_str)
|
40
|
+
|
41
|
+
lambda do |corrector|
|
42
|
+
new_source = receiver.source + '.include?(' +
|
43
|
+
to_string_literal(regex_str) + ')'
|
44
|
+
corrector.replace(node.source_range, new_source)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def literal?(regex_str)
|
52
|
+
regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|