rubocop-performance 1.5.2 → 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 +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +5 -1
- data/config/default.yml +96 -13
- 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 +48 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +45 -0
- data/lib/rubocop/cop/performance/bind_call.rb +77 -0
- data/lib/rubocop/cop/performance/caller.rb +5 -4
- data/lib/rubocop/cop/performance/case_when_splat.rb +18 -11
- data/lib/rubocop/cop/performance/casecmp.rb +17 -23
- data/lib/rubocop/cop/performance/chain_array_allocation.rb +5 -11
- data/lib/rubocop/cop/performance/collection_literal_in_loop.rb +140 -0
- data/lib/rubocop/cop/performance/compare_with_block.rb +12 -23
- data/lib/rubocop/cop/performance/count.rb +14 -17
- data/lib/rubocop/cop/performance/delete_prefix.rb +87 -0
- data/lib/rubocop/cop/performance/delete_suffix.rb +87 -0
- data/lib/rubocop/cop/performance/detect.rb +64 -32
- data/lib/rubocop/cop/performance/double_start_end_with.rb +18 -26
- data/lib/rubocop/cop/performance/end_with.rb +38 -25
- data/lib/rubocop/cop/performance/fixed_size.rb +2 -2
- data/lib/rubocop/cop/performance/flat_map.rb +21 -23
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +14 -15
- data/lib/rubocop/cop/performance/io_readlines.rb +116 -0
- data/lib/rubocop/cop/performance/open_struct.rb +3 -3
- data/lib/rubocop/cop/performance/range_include.rb +15 -12
- data/lib/rubocop/cop/performance/redundant_block_call.rb +14 -9
- data/lib/rubocop/cop/performance/redundant_match.rb +13 -8
- data/lib/rubocop/cop/performance/redundant_merge.rb +36 -23
- 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 +32 -32
- data/lib/rubocop/cop/performance/reverse_each.rb +10 -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 +38 -28
- data/lib/rubocop/cop/performance/string_include.rb +55 -0
- data/lib/rubocop/cop/performance/string_replacement.rb +25 -36
- data/lib/rubocop/cop/performance/sum.rb +134 -0
- data/lib/rubocop/cop/performance/times_map.rb +12 -19
- data/lib/rubocop/cop/performance/unfreeze_string.rb +4 -8
- data/lib/rubocop/cop/performance/uri_default_parser.rb +7 -13
- data/lib/rubocop/cop/performance_cops.rb +17 -0
- data/lib/rubocop/performance/inject.rb +1 -1
- data/lib/rubocop/performance/version.rb +1 -1
- metadata +41 -11
@@ -12,13 +12,14 @@ 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 = '_'
|
20
21
|
|
21
|
-
def_node_matcher :reverse_each?,
|
22
|
+
def_node_matcher :reverse_each?, <<~MATCHER
|
22
23
|
(send $(send _ :reverse) :each)
|
23
24
|
MATCHER
|
24
25
|
|
@@ -29,12 +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
|
-
|
39
|
+
private
|
40
|
+
|
41
|
+
def replacement_range(node)
|
42
|
+
range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
|
38
43
|
end
|
39
44
|
end
|
40
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
|
@@ -17,47 +20,54 @@ module RuboCop
|
|
17
20
|
#
|
18
21
|
# # good
|
19
22
|
# 'abc'.start_with?('ab')
|
20
|
-
|
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
|
+
#
|
44
|
+
class StartWith < Base
|
45
|
+
include RegexpMetacharacter
|
46
|
+
extend AutoCorrector
|
47
|
+
|
21
48
|
MSG = 'Use `String#start_with?` instead of a regex match anchored to ' \
|
22
49
|
'the beginning of the string.'
|
23
|
-
SINGLE_QUOTE = "'"
|
24
50
|
|
25
|
-
def_node_matcher :redundant_regex?,
|
51
|
+
def_node_matcher :redundant_regex?, <<~PATTERN
|
26
52
|
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_start?) (regopt)))
|
27
53
|
(send (regexp (str $#literal_at_start?) (regopt)) {:match :match?} $_)
|
28
54
|
(match-with-lvasgn (regexp (str $#literal_at_start?) (regopt)) $_)}
|
29
55
|
PATTERN
|
30
56
|
|
31
|
-
def literal_at_start?(regex_str)
|
32
|
-
# is this regexp 'literal' in the sense of only matching literal
|
33
|
-
# chars, rather than using metachars like `.` and `*` and so on?
|
34
|
-
# also, is it anchored at the start of the string?
|
35
|
-
# (tricky: \s, \d, and so on are metacharacters, but other characters
|
36
|
-
# escaped with a slash are just literals. LITERAL_REGEX takes all
|
37
|
-
# that into account.)
|
38
|
-
regex_str =~ /\A\\A(?:#{LITERAL_REGEX})+\z/
|
39
|
-
end
|
40
|
-
|
41
57
|
def on_send(node)
|
42
|
-
return unless redundant_regex?(node)
|
43
|
-
|
44
|
-
add_offense(node)
|
45
|
-
end
|
46
|
-
alias on_match_with_lvasgn on_send
|
58
|
+
return unless (receiver, regex_str = redundant_regex?(node))
|
47
59
|
|
48
|
-
|
49
|
-
redundant_regex?(node) do |receiver, regex_str|
|
60
|
+
add_offense(node) do |corrector|
|
50
61
|
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
|
51
|
-
regex_str = regex_str
|
62
|
+
regex_str = drop_start_metacharacter(regex_str)
|
52
63
|
regex_str = interpret_string_escapes(regex_str)
|
53
64
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
corrector.replace(node.source_range, new_source)
|
58
|
-
end
|
65
|
+
new_source = "#{receiver.source}.start_with?(#{to_string_literal(regex_str)})"
|
66
|
+
|
67
|
+
corrector.replace(node.source_range, new_source)
|
59
68
|
end
|
60
69
|
end
|
70
|
+
alias on_match_with_lvasgn on_send
|
61
71
|
end
|
62
72
|
end
|
63
73
|
end
|
@@ -0,0 +1,55 @@
|
|
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
|
+
# This cop's offenses are not safe to auto-correct if a receiver is nil.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# 'abc'.match?(/ab/)
|
14
|
+
# /ab/.match?('abc')
|
15
|
+
# 'abc' =~ /ab/
|
16
|
+
# /ab/ =~ 'abc'
|
17
|
+
# 'abc'.match(/ab/)
|
18
|
+
# /ab/.match('abc')
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# 'abc'.include?('ab')
|
22
|
+
class StringInclude < Base
|
23
|
+
extend AutoCorrector
|
24
|
+
|
25
|
+
MSG = 'Use `String#include?` instead of a regex match with literal-only pattern.'
|
26
|
+
|
27
|
+
def_node_matcher :redundant_regex?, <<~PATTERN
|
28
|
+
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal?) (regopt)))
|
29
|
+
(send (regexp (str $#literal?) (regopt)) {:match :match?} $str)
|
30
|
+
(match-with-lvasgn (regexp (str $#literal?) (regopt)) $_)}
|
31
|
+
PATTERN
|
32
|
+
|
33
|
+
def on_send(node)
|
34
|
+
return unless (receiver, regex_str = redundant_regex?(node))
|
35
|
+
|
36
|
+
add_offense(node) do |corrector|
|
37
|
+
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
|
38
|
+
regex_str = interpret_string_escapes(regex_str)
|
39
|
+
|
40
|
+
new_source = "#{receiver.source}.include?(#{to_string_literal(regex_str)})"
|
41
|
+
|
42
|
+
corrector.replace(node.source_range, new_source)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
alias on_match_with_lvasgn on_send
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def literal?(regex_str)
|
50
|
+
regex_str.match?(/\A#{Util::LITERAL_REGEX}+\z/)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|