rubocop-performance 1.5.1 → 1.7.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 +78 -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 +47 -0
- data/lib/rubocop/cop/performance/big_decimal_with_numeric_argument.rb +50 -0
- data/lib/rubocop/cop/performance/bind_call.rb +87 -0
- data/lib/rubocop/cop/performance/caller.rb +2 -2
- 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 +30 -12
- 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 +20 -7
- 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 +30 -15
- data/lib/rubocop/cop/performance/string_include.rb +59 -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
@@ -31,7 +31,7 @@ module RuboCop
|
|
31
31
|
MSG = 'Consider using `Struct` over `OpenStruct` ' \
|
32
32
|
'to optimize the performance.'
|
33
33
|
|
34
|
-
def_node_matcher :open_struct,
|
34
|
+
def_node_matcher :open_struct, <<~PATTERN
|
35
35
|
(send (const {nil? cbase} :OpenStruct) :new ...)
|
36
36
|
PATTERN
|
37
37
|
|
@@ -3,18 +3,19 @@
|
|
3
3
|
module RuboCop
|
4
4
|
module Cop
|
5
5
|
module Performance
|
6
|
-
# This cop identifies uses of `Range#include?`, which iterates over each
|
6
|
+
# This cop identifies uses of `Range#include?` and `Range#member?`, which iterates over each
|
7
7
|
# item in a `Range` to see if a specified item is there. In contrast,
|
8
8
|
# `Range#cover?` simply compares the target item with the beginning and
|
9
9
|
# end points of the `Range`. In a great majority of cases, this is what
|
10
10
|
# is wanted.
|
11
11
|
#
|
12
|
-
# This cop is `Safe: false` by default because `Range#include?` and
|
12
|
+
# This cop is `Safe: false` by default because `Range#include?` (or `Range#member?`) and
|
13
13
|
# `Range#cover?` are not equivalent behaviour.
|
14
14
|
#
|
15
15
|
# @example
|
16
16
|
# # bad
|
17
17
|
# ('a'..'z').include?('b') # => true
|
18
|
+
# ('a'..'z').member?('b') # => true
|
18
19
|
#
|
19
20
|
# # good
|
20
21
|
# ('a'..'z').cover?('b') # => true
|
@@ -24,21 +25,22 @@ module RuboCop
|
|
24
25
|
#
|
25
26
|
# ('a'..'z').cover?('yellow') # => true
|
26
27
|
class RangeInclude < Cop
|
27
|
-
MSG = 'Use `Range#cover?` instead of `Range
|
28
|
+
MSG = 'Use `Range#cover?` instead of `Range#%<bad_method>s`.'
|
28
29
|
|
29
30
|
# TODO: If we traced out assignments of variables to their uses, we
|
30
31
|
# might pick up on a few more instances of this issue
|
31
32
|
# Right now, we only detect direct calls on a Range literal
|
32
33
|
# (We don't even catch it if the Range is in double parens)
|
33
34
|
|
34
|
-
def_node_matcher :range_include,
|
35
|
-
(send {irange erange (begin {irange erange})} :include? ...)
|
35
|
+
def_node_matcher :range_include, <<~PATTERN
|
36
|
+
(send {irange erange (begin {irange erange})} ${:include? :member?} ...)
|
36
37
|
PATTERN
|
37
38
|
|
38
39
|
def on_send(node)
|
39
|
-
|
40
|
-
|
41
|
-
|
40
|
+
range_include(node) do |bad_method|
|
41
|
+
message = format(MSG, bad_method: bad_method)
|
42
|
+
add_offense(node, location: :selector, message: message)
|
43
|
+
end
|
42
44
|
end
|
43
45
|
|
44
46
|
def autocorrect(node)
|
@@ -29,16 +29,16 @@ module RuboCop
|
|
29
29
|
CLOSE_PAREN = ')'
|
30
30
|
SPACE = ' '
|
31
31
|
|
32
|
-
def_node_matcher :blockarg_def,
|
32
|
+
def_node_matcher :blockarg_def, <<~PATTERN
|
33
33
|
{(def _ (args ... (blockarg $_)) $_)
|
34
34
|
(defs _ _ (args ... (blockarg $_)) $_)}
|
35
35
|
PATTERN
|
36
36
|
|
37
|
-
def_node_search :blockarg_calls,
|
37
|
+
def_node_search :blockarg_calls, <<~PATTERN
|
38
38
|
(send (lvar %1) :call ...)
|
39
39
|
PATTERN
|
40
40
|
|
41
|
-
def_node_search :blockarg_assigned?,
|
41
|
+
def_node_search :blockarg_assigned?, <<~PATTERN
|
42
42
|
(lvasgn %1 ...)
|
43
43
|
PATTERN
|
44
44
|
|
@@ -23,12 +23,12 @@ module RuboCop
|
|
23
23
|
|
24
24
|
# 'match' is a fairly generic name, so we don't flag it unless we see
|
25
25
|
# a string or regexp literal on one side or the other
|
26
|
-
def_node_matcher :match_call?,
|
26
|
+
def_node_matcher :match_call?, <<~PATTERN
|
27
27
|
{(send {str regexp} :match _)
|
28
28
|
(send !nil? :match {str regexp})}
|
29
29
|
PATTERN
|
30
30
|
|
31
|
-
def_node_matcher :only_truthiness_matters?,
|
31
|
+
def_node_matcher :only_truthiness_matters?, <<~PATTERN
|
32
32
|
^({if while until case while_post until_post} equal?(%0) ...)
|
33
33
|
PATTERN
|
34
34
|
|
@@ -5,11 +5,25 @@ module RuboCop
|
|
5
5
|
module Performance
|
6
6
|
# This cop identifies places where `Hash#merge!` can be replaced by
|
7
7
|
# `Hash#[]=`.
|
8
|
+
# You can set the maximum number of key-value pairs to consider
|
9
|
+
# an offense with `MaxKeyValuePairs`.
|
8
10
|
#
|
9
11
|
# @example
|
12
|
+
# # bad
|
10
13
|
# hash.merge!(a: 1)
|
11
14
|
# hash.merge!({'key' => 'value'})
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# hash[:a] = 1
|
18
|
+
# hash['key'] = 'value'
|
19
|
+
#
|
20
|
+
# @example MaxKeyValuePairs: 2 (default)
|
21
|
+
# # bad
|
12
22
|
# hash.merge!(a: 1, b: 2)
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
# hash[:a] = 1
|
26
|
+
# hash[:b] = 2
|
13
27
|
class RedundantMerge < Cop
|
14
28
|
AREF_ASGN = '%<receiver>s[%<key>s] = %<value>s'
|
15
29
|
MSG = 'Use `%<prefer>s` instead of `%<current>s`.'
|
@@ -20,11 +34,11 @@ module RuboCop
|
|
20
34
|
%<leading_space>send
|
21
35
|
RUBY
|
22
36
|
|
23
|
-
def_node_matcher :redundant_merge_candidate,
|
37
|
+
def_node_matcher :redundant_merge_candidate, <<~PATTERN
|
24
38
|
(send $!nil? :merge! [(hash $...) !kwsplat_type?])
|
25
39
|
PATTERN
|
26
40
|
|
27
|
-
def_node_matcher :modifier_flow_control?,
|
41
|
+
def_node_matcher :modifier_flow_control?, <<~PATTERN
|
28
42
|
[{if while until} modifier_form?]
|
29
43
|
PATTERN
|
30
44
|
|
@@ -65,7 +79,8 @@ module RuboCop
|
|
65
79
|
end
|
66
80
|
|
67
81
|
def non_redundant_merge?(node, receiver, pairs)
|
68
|
-
|
82
|
+
pairs.empty? ||
|
83
|
+
non_redundant_pairs?(receiver, pairs) ||
|
69
84
|
kwsplat_used?(pairs) ||
|
70
85
|
non_redundant_value_used?(receiver, node)
|
71
86
|
end
|
@@ -167,13 +182,11 @@ module RuboCop
|
|
167
182
|
end
|
168
183
|
|
169
184
|
def unwind(receiver)
|
170
|
-
while receiver.respond_to?(:send_type?) && receiver.send_type?
|
171
|
-
receiver, = *receiver
|
172
|
-
end
|
185
|
+
receiver, = *receiver while receiver.respond_to?(:send_type?) && receiver.send_type?
|
173
186
|
receiver
|
174
187
|
end
|
175
188
|
|
176
|
-
def_node_matcher :each_with_object_node,
|
189
|
+
def_node_matcher :each_with_object_node, <<~PATTERN
|
177
190
|
(block (send _ :each_with_object _) (args _ $_) ...)
|
178
191
|
PATTERN
|
179
192
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies places where `sort { |a, b| a <=> b }`
|
7
|
+
# can be replaced with `sort`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# array.sort { |a, b| a <=> b }
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# array.sort
|
15
|
+
#
|
16
|
+
class RedundantSortBlock < Cop
|
17
|
+
include SortBlock
|
18
|
+
|
19
|
+
MSG = 'Use `sort` 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_a, var_b) 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
|
+
corrector.replace(range, 'sort')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def message(var_a, var_b)
|
47
|
+
bad_method = "sort { |#{var_a}, #{var_b}| #{var_a} <=> #{var_b} }"
|
48
|
+
format(MSG, bad_method: bad_method)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop checks for redundant `String#chars`.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
# str.chars[0..2]
|
11
|
+
# str.chars.slice(0..2)
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# str[0..2].chars
|
15
|
+
#
|
16
|
+
# # bad
|
17
|
+
# str.chars.first
|
18
|
+
# str.chars.first(2)
|
19
|
+
# str.chars.last
|
20
|
+
# str.chars.last(2)
|
21
|
+
#
|
22
|
+
# # good
|
23
|
+
# str[0]
|
24
|
+
# str[0...2].chars
|
25
|
+
# str[-1]
|
26
|
+
# str[-2..-1].chars
|
27
|
+
#
|
28
|
+
# # bad
|
29
|
+
# str.chars.take(2)
|
30
|
+
# str.chars.drop(2)
|
31
|
+
# str.chars.length
|
32
|
+
# str.chars.size
|
33
|
+
# str.chars.empty?
|
34
|
+
#
|
35
|
+
# # good
|
36
|
+
# str[0...2].chars
|
37
|
+
# str[2..-1].chars
|
38
|
+
# str.length
|
39
|
+
# str.size
|
40
|
+
# str.empty?
|
41
|
+
#
|
42
|
+
class RedundantStringChars < Cop
|
43
|
+
include RangeHelp
|
44
|
+
|
45
|
+
MSG = 'Use `%<good_method>s` instead of `%<bad_method>s`.'
|
46
|
+
REPLACEABLE_METHODS = %i[[] slice first last take drop length size empty?].freeze
|
47
|
+
|
48
|
+
def_node_matcher :redundant_chars_call?, <<~PATTERN
|
49
|
+
(send $(send _ :chars) $#replaceable_method? $...)
|
50
|
+
PATTERN
|
51
|
+
|
52
|
+
def on_send(node)
|
53
|
+
redundant_chars_call?(node) do |receiver, method, args|
|
54
|
+
range = offense_range(receiver, node)
|
55
|
+
message = build_message(method, args)
|
56
|
+
add_offense(node, location: range, message: message)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def autocorrect(node)
|
61
|
+
redundant_chars_call?(node) do |receiver, method, args|
|
62
|
+
range = correction_range(receiver, node)
|
63
|
+
replacement = build_good_method(method, args)
|
64
|
+
|
65
|
+
lambda do |corrector|
|
66
|
+
corrector.replace(range, replacement)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def replaceable_method?(method_name)
|
74
|
+
REPLACEABLE_METHODS.include?(method_name)
|
75
|
+
end
|
76
|
+
|
77
|
+
def offense_range(receiver, node)
|
78
|
+
range_between(receiver.loc.selector.begin_pos, node.loc.expression.end_pos)
|
79
|
+
end
|
80
|
+
|
81
|
+
def correction_range(receiver, node)
|
82
|
+
range_between(receiver.loc.dot.begin_pos, node.loc.expression.end_pos)
|
83
|
+
end
|
84
|
+
|
85
|
+
def build_message(method, args)
|
86
|
+
good_method = build_good_method(method, args)
|
87
|
+
bad_method = build_bad_method(method, args)
|
88
|
+
format(MSG, good_method: good_method, bad_method: bad_method)
|
89
|
+
end
|
90
|
+
|
91
|
+
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
92
|
+
def build_good_method(method, args)
|
93
|
+
case method
|
94
|
+
when :[], :slice
|
95
|
+
"[#{build_call_args(args)}].chars"
|
96
|
+
when :first
|
97
|
+
if args.any?
|
98
|
+
"[0...#{args.first.source}].chars"
|
99
|
+
else
|
100
|
+
'[0]'
|
101
|
+
end
|
102
|
+
when :last
|
103
|
+
if args.any?
|
104
|
+
"[-#{args.first.source}..-1].chars"
|
105
|
+
else
|
106
|
+
'[-1]'
|
107
|
+
end
|
108
|
+
when :take
|
109
|
+
"[0...#{args.first.source}].chars"
|
110
|
+
when :drop
|
111
|
+
"[#{args.first.source}..-1].chars"
|
112
|
+
else
|
113
|
+
".#{method}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
117
|
+
|
118
|
+
def build_bad_method(method, args)
|
119
|
+
case method
|
120
|
+
when :[]
|
121
|
+
"chars[#{build_call_args(args)}]"
|
122
|
+
else
|
123
|
+
if args.any?
|
124
|
+
"chars.#{method}(#{build_call_args(args)})"
|
125
|
+
else
|
126
|
+
"chars.#{method}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def build_call_args(call_args_node)
|
132
|
+
call_args_node.map(&:source).join(', ')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
@@ -73,32 +73,28 @@ module RuboCop
|
|
73
73
|
# end
|
74
74
|
# end
|
75
75
|
class RegexpMatch < Cop
|
76
|
-
extend TargetRubyVersion
|
77
|
-
|
78
|
-
minimum_target_ruby_version 2.4
|
79
|
-
|
80
76
|
# Constants are included in this list because it is unlikely that
|
81
77
|
# someone will store `nil` as a constant and then use it for comparison
|
82
78
|
TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
|
83
79
|
MSG = 'Use `match?` instead of `%<current>s` when `MatchData` ' \
|
84
80
|
'is not used.'
|
85
81
|
|
86
|
-
def_node_matcher :match_method?,
|
82
|
+
def_node_matcher :match_method?, <<~PATTERN
|
87
83
|
{
|
88
84
|
(send _recv :match {regexp str sym})
|
89
85
|
(send {regexp str sym} :match _)
|
90
86
|
}
|
91
87
|
PATTERN
|
92
88
|
|
93
|
-
def_node_matcher :match_with_int_arg_method?,
|
89
|
+
def_node_matcher :match_with_int_arg_method?, <<~PATTERN
|
94
90
|
(send _recv :match _ (int ...))
|
95
91
|
PATTERN
|
96
92
|
|
97
|
-
def_node_matcher :match_operator?,
|
93
|
+
def_node_matcher :match_operator?, <<~PATTERN
|
98
94
|
(send !nil? {:=~ :!~} !nil?)
|
99
95
|
PATTERN
|
100
96
|
|
101
|
-
def_node_matcher :match_threequals?,
|
97
|
+
def_node_matcher :match_threequals?, <<~PATTERN
|
102
98
|
(send (regexp (str _) {(regopt) (regopt _)}) :=== !nil?)
|
103
99
|
PATTERN
|
104
100
|
|
@@ -109,7 +105,7 @@ module RuboCop
|
|
109
105
|
regexp.to_regexp.named_captures.empty?
|
110
106
|
end
|
111
107
|
|
112
|
-
MATCH_NODE_PATTERN =
|
108
|
+
MATCH_NODE_PATTERN = <<~PATTERN
|
113
109
|
{
|
114
110
|
#match_method?
|
115
111
|
#match_with_int_arg_method?
|
@@ -122,7 +118,7 @@ module RuboCop
|
|
122
118
|
def_node_matcher :match_node?, MATCH_NODE_PATTERN
|
123
119
|
def_node_search :search_match_nodes, MATCH_NODE_PATTERN
|
124
120
|
|
125
|
-
def_node_search :last_matches,
|
121
|
+
def_node_search :last_matches, <<~PATTERN
|
126
122
|
{
|
127
123
|
(send (const nil? :Regexp) :last_match)
|
128
124
|
(send (const nil? :Regexp) :last_match _)
|
@@ -256,6 +252,13 @@ module RuboCop
|
|
256
252
|
def correct_operator(corrector, recv, arg, oper = nil)
|
257
253
|
op_range = correction_range(recv, arg)
|
258
254
|
|
255
|
+
replace_with_match_predicate_method(corrector, recv, arg, op_range)
|
256
|
+
|
257
|
+
corrector.insert_after(arg.loc.expression, ')') unless op_range.source.end_with?('(')
|
258
|
+
corrector.insert_before(recv.loc.expression, '!') if oper == :!~
|
259
|
+
end
|
260
|
+
|
261
|
+
def replace_with_match_predicate_method(corrector, recv, arg, op_range)
|
259
262
|
if TYPES_IMPLEMENTING_MATCH.include?(recv.type)
|
260
263
|
corrector.replace(op_range, '.match?(')
|
261
264
|
elsif TYPES_IMPLEMENTING_MATCH.include?(arg.type)
|
@@ -264,9 +267,6 @@ module RuboCop
|
|
264
267
|
else
|
265
268
|
corrector.replace(op_range, '&.match?(')
|
266
269
|
end
|
267
|
-
|
268
|
-
corrector.insert_after(arg.loc.expression, ')')
|
269
|
-
corrector.insert_before(recv.loc.expression, '!') if oper == :!~
|
270
270
|
end
|
271
271
|
|
272
272
|
def swap_receiver_and_arg(corrector, recv, arg)
|
@@ -18,7 +18,7 @@ module RuboCop
|
|
18
18
|
MSG = 'Use `reverse_each` instead of `reverse.each`.'
|
19
19
|
UNDERSCORE = '_'
|
20
20
|
|
21
|
-
def_node_matcher :reverse_each?,
|
21
|
+
def_node_matcher :reverse_each?, <<~MATCHER
|
22
22
|
(send $(send _ :reverse) :each)
|
23
23
|
MATCHER
|
24
24
|
|
@@ -34,7 +34,8 @@ module RuboCop
|
|
34
34
|
end
|
35
35
|
|
36
36
|
def autocorrect(node)
|
37
|
-
|
37
|
+
range = range_between(node.loc.dot.begin_pos, node.loc.selector.begin_pos)
|
38
|
+
->(corrector) { corrector.replace(range, UNDERSCORE) }
|
38
39
|
end
|
39
40
|
end
|
40
41
|
end
|