rubocop-performance 0.0.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 +7 -0
- data/LICENSE.txt +20 -0
- data/README.md +1 -0
- data/lib/rubocop-performance.rb +5 -0
- data/lib/rubocop/cop/performance/caller.rb +69 -0
- data/lib/rubocop/cop/performance/case_when_splat.rb +173 -0
- data/lib/rubocop/cop/performance/casecmp.rb +117 -0
- data/lib/rubocop/cop/performance/compare_with_block.rb +119 -0
- data/lib/rubocop/cop/performance/count.rb +102 -0
- data/lib/rubocop/cop/performance/detect.rb +110 -0
- data/lib/rubocop/cop/performance/double_start_end_with.rb +94 -0
- data/lib/rubocop/cop/performance/end_with.rb +56 -0
- data/lib/rubocop/cop/performance/fixed_size.rb +97 -0
- data/lib/rubocop/cop/performance/flat_map.rb +78 -0
- data/lib/rubocop/cop/performance/inefficient_hash_search.rb +99 -0
- data/lib/rubocop/cop/performance/lstrip_rstrip.rb +46 -0
- data/lib/rubocop/cop/performance/range_include.rb +47 -0
- data/lib/rubocop/cop/performance/redundant_block_call.rb +93 -0
- data/lib/rubocop/cop/performance/redundant_match.rb +56 -0
- data/lib/rubocop/cop/performance/redundant_merge.rb +169 -0
- data/lib/rubocop/cop/performance/redundant_sort_by.rb +50 -0
- data/lib/rubocop/cop/performance/regexp_match.rb +264 -0
- data/lib/rubocop/cop/performance/reverse_each.rb +42 -0
- data/lib/rubocop/cop/performance/sample.rb +142 -0
- data/lib/rubocop/cop/performance/size.rb +71 -0
- data/lib/rubocop/cop/performance/start_with.rb +59 -0
- data/lib/rubocop/cop/performance/string_replacement.rb +172 -0
- data/lib/rubocop/cop/performance/times_map.rb +71 -0
- data/lib/rubocop/cop/performance/unfreeze_string.rb +50 -0
- data/lib/rubocop/cop/performance/unneeded_sort.rb +165 -0
- data/lib/rubocop/cop/performance/uri_default_parser.rb +47 -0
- data/lib/rubocop/cop/performance_cops.rb +37 -0
- data/lib/rubocop/performance/version.rb +9 -0
- metadata +114 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop identifies places where `sort_by { ... }` can be replaced by
|
7
|
+
# `sort`.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# array.sort_by { |x| x }
|
12
|
+
# array.sort_by do |var|
|
13
|
+
# var
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# array.sort
|
18
|
+
class RedundantSortBy < Cop
|
19
|
+
include RangeHelp
|
20
|
+
|
21
|
+
MSG = 'Use `sort` instead of `sort_by { |%<var>s| %<var>s }`.'.freeze
|
22
|
+
|
23
|
+
def_node_matcher :redundant_sort_by, <<-PATTERN
|
24
|
+
(block $(send _ :sort_by) (args (arg $_x)) (lvar _x))
|
25
|
+
PATTERN
|
26
|
+
|
27
|
+
def on_block(node)
|
28
|
+
redundant_sort_by(node) do |send, var_name|
|
29
|
+
range = sort_by_range(send, node)
|
30
|
+
|
31
|
+
add_offense(node,
|
32
|
+
location: range,
|
33
|
+
message: format(MSG, var: var_name))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def autocorrect(node)
|
38
|
+
send, = *node
|
39
|
+
->(corrector) { corrector.replace(sort_by_range(send, node), 'sort') }
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def sort_by_range(send, node)
|
45
|
+
range_between(send.loc.selector.begin_pos, node.loc.end.end_pos)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# In Ruby 2.4, `String#match?`, `Regexp#match?` and `Symbol#match?`
|
7
|
+
# have been added. The methods are faster than `match`.
|
8
|
+
# Because the methods avoid creating a `MatchData` object or saving
|
9
|
+
# backref.
|
10
|
+
# So, when `MatchData` is not used, use `match?` instead of `match`.
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# # bad
|
14
|
+
# def foo
|
15
|
+
# if x =~ /re/
|
16
|
+
# do_something
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # bad
|
21
|
+
# def foo
|
22
|
+
# if x !~ /re/
|
23
|
+
# do_something
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # bad
|
28
|
+
# def foo
|
29
|
+
# if x.match(/re/)
|
30
|
+
# do_something
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # bad
|
35
|
+
# def foo
|
36
|
+
# if /re/ === x
|
37
|
+
# do_something
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# # good
|
42
|
+
# def foo
|
43
|
+
# if x.match?(/re/)
|
44
|
+
# do_something
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# # good
|
49
|
+
# def foo
|
50
|
+
# if !x.match?(/re/)
|
51
|
+
# do_something
|
52
|
+
# end
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# # good
|
56
|
+
# def foo
|
57
|
+
# if x =~ /re/
|
58
|
+
# do_something(Regexp.last_match)
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# # good
|
63
|
+
# def foo
|
64
|
+
# if x.match(/re/)
|
65
|
+
# do_something($~)
|
66
|
+
# end
|
67
|
+
# end
|
68
|
+
#
|
69
|
+
# # good
|
70
|
+
# def foo
|
71
|
+
# if /re/ === x
|
72
|
+
# do_something($~)
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
class RegexpMatch < Cop
|
76
|
+
extend TargetRubyVersion
|
77
|
+
|
78
|
+
minimum_target_ruby_version 2.4
|
79
|
+
|
80
|
+
# Constants are included in this list because it is unlikely that
|
81
|
+
# someone will store `nil` as a constant and then use it for comparison
|
82
|
+
TYPES_IMPLEMENTING_MATCH = %i[const regexp str sym].freeze
|
83
|
+
MSG =
|
84
|
+
'Use `match?` instead of `%<current>s` when `MatchData` ' \
|
85
|
+
'is not used.'.freeze
|
86
|
+
|
87
|
+
def_node_matcher :match_method?, <<-PATTERN
|
88
|
+
{
|
89
|
+
(send _recv :match _)
|
90
|
+
(send _recv :match _ (int ...))
|
91
|
+
}
|
92
|
+
PATTERN
|
93
|
+
|
94
|
+
def_node_matcher :match_operator?, <<-PATTERN
|
95
|
+
(send !nil? {:=~ :!~} !nil?)
|
96
|
+
PATTERN
|
97
|
+
|
98
|
+
def_node_matcher :match_threequals?, <<-PATTERN
|
99
|
+
(send (regexp (str _) {(regopt) (regopt _)}) :=== !nil?)
|
100
|
+
PATTERN
|
101
|
+
|
102
|
+
def match_with_lvasgn?(node)
|
103
|
+
return false unless node.match_with_lvasgn_type?
|
104
|
+
regexp, _rhs = *node
|
105
|
+
regexp.to_regexp.named_captures.empty?
|
106
|
+
end
|
107
|
+
|
108
|
+
MATCH_NODE_PATTERN = <<-PATTERN.freeze
|
109
|
+
{
|
110
|
+
#match_method?
|
111
|
+
#match_operator?
|
112
|
+
#match_threequals?
|
113
|
+
#match_with_lvasgn?
|
114
|
+
}
|
115
|
+
PATTERN
|
116
|
+
|
117
|
+
def_node_matcher :match_node?, MATCH_NODE_PATTERN
|
118
|
+
def_node_search :search_match_nodes, MATCH_NODE_PATTERN
|
119
|
+
|
120
|
+
def_node_search :last_matches, <<-PATTERN
|
121
|
+
{
|
122
|
+
(send (const nil? :Regexp) :last_match)
|
123
|
+
(send (const nil? :Regexp) :last_match _)
|
124
|
+
({back_ref nth_ref} _)
|
125
|
+
(gvar #match_gvar?)
|
126
|
+
}
|
127
|
+
PATTERN
|
128
|
+
|
129
|
+
def on_if(node)
|
130
|
+
check_condition(node.condition)
|
131
|
+
end
|
132
|
+
|
133
|
+
def on_case(node)
|
134
|
+
return if node.condition
|
135
|
+
|
136
|
+
node.each_when do |when_node|
|
137
|
+
when_node.each_condition do |condition|
|
138
|
+
check_condition(condition)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def autocorrect(node)
|
144
|
+
lambda do |corrector|
|
145
|
+
if match_method?(node)
|
146
|
+
corrector.replace(node.loc.selector, 'match?')
|
147
|
+
elsif match_operator?(node) || match_threequals?(node)
|
148
|
+
recv, oper, arg = *node
|
149
|
+
correct_operator(corrector, recv, arg, oper)
|
150
|
+
elsif match_with_lvasgn?(node)
|
151
|
+
recv, arg = *node
|
152
|
+
correct_operator(corrector, recv, arg)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def check_condition(cond)
|
160
|
+
match_node?(cond) do
|
161
|
+
return if last_match_used?(cond)
|
162
|
+
|
163
|
+
add_offense(cond)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def message(node)
|
168
|
+
format(MSG, current: node.loc.selector.source)
|
169
|
+
end
|
170
|
+
|
171
|
+
def last_match_used?(match_node)
|
172
|
+
scope_root = scope_root(match_node)
|
173
|
+
body = scope_root ? scope_body(scope_root) : match_node.ancestors.last
|
174
|
+
|
175
|
+
return true if match_node.parent.if_type? &&
|
176
|
+
match_node.parent.modifier_form?
|
177
|
+
|
178
|
+
match_node_pos = match_node.loc.expression.begin_pos
|
179
|
+
|
180
|
+
next_match_pos = next_match_pos(body, match_node_pos, scope_root)
|
181
|
+
range = match_node_pos..next_match_pos
|
182
|
+
|
183
|
+
find_last_match(body, range, scope_root)
|
184
|
+
end
|
185
|
+
|
186
|
+
def next_match_pos(body, match_node_pos, scope_root)
|
187
|
+
node = search_match_nodes(body).find do |match|
|
188
|
+
match.loc.expression.begin_pos > match_node_pos &&
|
189
|
+
scope_root(match) == scope_root
|
190
|
+
end
|
191
|
+
node ? node.loc.expression.begin_pos : Float::INFINITY
|
192
|
+
end
|
193
|
+
|
194
|
+
def find_last_match(body, range, scope_root)
|
195
|
+
last_matches(body).find do |ref|
|
196
|
+
ref_pos = ref.loc.expression.begin_pos
|
197
|
+
range.cover?(ref_pos) &&
|
198
|
+
scope_root(ref) == scope_root
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def scope_body(node)
|
203
|
+
children = node.children
|
204
|
+
case node.type
|
205
|
+
when :module
|
206
|
+
children[1]
|
207
|
+
when :defs
|
208
|
+
children[3]
|
209
|
+
else
|
210
|
+
children[2]
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def scope_root(node)
|
215
|
+
node.each_ancestor.find do |ancestor|
|
216
|
+
ancestor.def_type? ||
|
217
|
+
ancestor.defs_type? ||
|
218
|
+
ancestor.class_type? ||
|
219
|
+
ancestor.module_type?
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def match_gvar?(sym)
|
224
|
+
%i[
|
225
|
+
$~
|
226
|
+
$MATCH
|
227
|
+
$PREMATCH
|
228
|
+
$POSTMATCH
|
229
|
+
$LAST_PAREN_MATCH
|
230
|
+
$LAST_MATCH_INFO
|
231
|
+
].include?(sym)
|
232
|
+
end
|
233
|
+
|
234
|
+
def correct_operator(corrector, recv, arg, oper = nil)
|
235
|
+
op_range = correction_range(recv, arg)
|
236
|
+
|
237
|
+
if TYPES_IMPLEMENTING_MATCH.include?(recv.type)
|
238
|
+
corrector.replace(op_range, '.match?(')
|
239
|
+
elsif TYPES_IMPLEMENTING_MATCH.include?(arg.type)
|
240
|
+
corrector.replace(op_range, '.match?(')
|
241
|
+
swap_receiver_and_arg(corrector, recv, arg)
|
242
|
+
else
|
243
|
+
corrector.replace(op_range, '&.match?(')
|
244
|
+
end
|
245
|
+
|
246
|
+
corrector.insert_after(arg.loc.expression, ')')
|
247
|
+
corrector.insert_before(recv.loc.expression, '!') if oper == :!~
|
248
|
+
end
|
249
|
+
|
250
|
+
def swap_receiver_and_arg(corrector, recv, arg)
|
251
|
+
corrector.replace(recv.loc.expression, arg.source)
|
252
|
+
corrector.replace(arg.loc.expression, recv.source)
|
253
|
+
end
|
254
|
+
|
255
|
+
def correction_range(recv, arg)
|
256
|
+
buffer = processed_source.buffer
|
257
|
+
op_begin_pos = recv.loc.expression.end_pos
|
258
|
+
op_end_pos = arg.loc.expression.begin_pos
|
259
|
+
Parser::Source::Range.new(buffer, op_begin_pos, op_end_pos)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop is used to identify usages of `reverse.each` and
|
7
|
+
# change them to use `reverse_each` instead.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# [].reverse.each
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# [].reverse_each
|
15
|
+
class ReverseEach < Cop
|
16
|
+
include RangeHelp
|
17
|
+
|
18
|
+
MSG = 'Use `reverse_each` instead of `reverse.each`.'.freeze
|
19
|
+
UNDERSCORE = '_'.freeze
|
20
|
+
|
21
|
+
def_node_matcher :reverse_each?, <<-MATCHER
|
22
|
+
(send $(send _ :reverse) :each)
|
23
|
+
MATCHER
|
24
|
+
|
25
|
+
def on_send(node)
|
26
|
+
reverse_each?(node) do |receiver|
|
27
|
+
location_of_reverse = receiver.loc.selector.begin_pos
|
28
|
+
end_location = node.loc.selector.end_pos
|
29
|
+
|
30
|
+
range = range_between(location_of_reverse, end_location)
|
31
|
+
|
32
|
+
add_offense(node, location: range)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def autocorrect(node)
|
37
|
+
->(corrector) { corrector.replace(node.loc.dot, UNDERSCORE) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop is used to identify usages of `shuffle.first`, `shuffle.last`
|
7
|
+
# and `shuffle[]` and change them to use `sample` instead.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# [1, 2, 3].shuffle.first
|
12
|
+
# [1, 2, 3].shuffle.first(2)
|
13
|
+
# [1, 2, 3].shuffle.last
|
14
|
+
# [2, 1, 3].shuffle.at(0)
|
15
|
+
# [2, 1, 3].shuffle.slice(0)
|
16
|
+
# [1, 2, 3].shuffle[2]
|
17
|
+
# [1, 2, 3].shuffle[0, 2] # sample(2) will do the same
|
18
|
+
# [1, 2, 3].shuffle[0..2] # sample(3) will do the same
|
19
|
+
# [1, 2, 3].shuffle(random: Random.new).first
|
20
|
+
#
|
21
|
+
# # good
|
22
|
+
# [1, 2, 3].shuffle
|
23
|
+
# [1, 2, 3].sample
|
24
|
+
# [1, 2, 3].sample(3)
|
25
|
+
# [1, 2, 3].shuffle[1, 3] # sample(3) might return a longer Array
|
26
|
+
# [1, 2, 3].shuffle[1..3] # sample(3) might return a longer Array
|
27
|
+
# [1, 2, 3].shuffle[foo, bar]
|
28
|
+
# [1, 2, 3].shuffle(random: Random.new)
|
29
|
+
class Sample < Cop
|
30
|
+
MSG = 'Use `%<correct>s` instead of `%<incorrect>s`.'.freeze
|
31
|
+
|
32
|
+
def_node_matcher :sample_candidate?, <<-PATTERN
|
33
|
+
(send $(send _ :shuffle $...) ${:first :last :[] :at :slice} $...)
|
34
|
+
PATTERN
|
35
|
+
|
36
|
+
def on_send(node)
|
37
|
+
sample_candidate?(node) do |shuffle, shuffle_arg, method, method_args|
|
38
|
+
return unless offensive?(method, method_args)
|
39
|
+
|
40
|
+
range = source_range(shuffle, node)
|
41
|
+
message = message(shuffle_arg, method, method_args, range)
|
42
|
+
add_offense(node, location: range, message: message)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def autocorrect(node)
|
47
|
+
shuffle_node, shuffle_arg, method, method_args =
|
48
|
+
sample_candidate?(node)
|
49
|
+
|
50
|
+
lambda do |corrector|
|
51
|
+
corrector.replace(source_range(shuffle_node, node),
|
52
|
+
correction(shuffle_arg, method, method_args))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def offensive?(method, method_args)
|
59
|
+
case method
|
60
|
+
when :first, :last
|
61
|
+
true
|
62
|
+
when :[], :at, :slice
|
63
|
+
sample_size(method_args) != :unknown
|
64
|
+
else
|
65
|
+
false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def sample_size(method_args)
|
70
|
+
case method_args.size
|
71
|
+
when 1
|
72
|
+
sample_size_for_one_arg(method_args.first)
|
73
|
+
when 2
|
74
|
+
sample_size_for_two_args(*method_args)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def sample_size_for_one_arg(arg)
|
79
|
+
case arg.type
|
80
|
+
when :erange, :irange
|
81
|
+
range_size(arg)
|
82
|
+
when :int
|
83
|
+
[0, -1].include?(arg.to_a.first) ? nil : :unknown
|
84
|
+
else
|
85
|
+
:unknown
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def sample_size_for_two_args(first, second)
|
90
|
+
return :unknown unless first.int_type? && first.to_a.first.zero?
|
91
|
+
second.int_type? ? second.to_a.first : :unknown
|
92
|
+
end
|
93
|
+
|
94
|
+
def range_size(range_node)
|
95
|
+
vals = range_node.to_a
|
96
|
+
return :unknown unless vals.all?(&:int_type?)
|
97
|
+
low, high = vals.map { |val| val.children[0] }
|
98
|
+
return :unknown unless low.zero? && high >= 0
|
99
|
+
|
100
|
+
case range_node.type
|
101
|
+
when :erange
|
102
|
+
(low...high).size
|
103
|
+
when :irange
|
104
|
+
(low..high).size
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def source_range(shuffle_node, node)
|
109
|
+
Parser::Source::Range.new(shuffle_node.source_range.source_buffer,
|
110
|
+
shuffle_node.loc.selector.begin_pos,
|
111
|
+
node.source_range.end_pos)
|
112
|
+
end
|
113
|
+
|
114
|
+
def message(shuffle_arg, method, method_args, range)
|
115
|
+
format(MSG,
|
116
|
+
correct: correction(shuffle_arg, method, method_args),
|
117
|
+
incorrect: range.source)
|
118
|
+
end
|
119
|
+
|
120
|
+
def correction(shuffle_arg, method, method_args)
|
121
|
+
shuffle_arg = extract_source(shuffle_arg)
|
122
|
+
sample_arg = sample_arg(method, method_args)
|
123
|
+
args = [sample_arg, shuffle_arg].compact.join(', ')
|
124
|
+
args.empty? ? 'sample' : "sample(#{args})"
|
125
|
+
end
|
126
|
+
|
127
|
+
def sample_arg(method, method_args)
|
128
|
+
case method
|
129
|
+
when :first, :last
|
130
|
+
extract_source(method_args)
|
131
|
+
when :[], :slice
|
132
|
+
sample_size(method_args)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def extract_source(args)
|
137
|
+
args.empty? ? nil : args.first.source
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|