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,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop is used to identify usages of `count` on an `Enumerable` that
|
7
|
+
# follow calls to `select` or `reject`. Querying logic can instead be
|
8
|
+
# passed to the `count` call.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# [1, 2, 3].select { |e| e > 2 }.size
|
13
|
+
# [1, 2, 3].reject { |e| e > 2 }.size
|
14
|
+
# [1, 2, 3].select { |e| e > 2 }.length
|
15
|
+
# [1, 2, 3].reject { |e| e > 2 }.length
|
16
|
+
# [1, 2, 3].select { |e| e > 2 }.count { |e| e.odd? }
|
17
|
+
# [1, 2, 3].reject { |e| e > 2 }.count { |e| e.even? }
|
18
|
+
# array.select(&:value).count
|
19
|
+
#
|
20
|
+
# # good
|
21
|
+
# [1, 2, 3].count { |e| e > 2 }
|
22
|
+
# [1, 2, 3].count { |e| e < 2 }
|
23
|
+
# [1, 2, 3].count { |e| e > 2 && e.odd? }
|
24
|
+
# [1, 2, 3].count { |e| e < 2 && e.even? }
|
25
|
+
# Model.select('field AS field_one').count
|
26
|
+
# Model.select(:value).count
|
27
|
+
#
|
28
|
+
# `ActiveRecord` compatibility:
|
29
|
+
# `ActiveRecord` will ignore the block that is passed to `count`.
|
30
|
+
# Other methods, such as `select`, will convert the association to an
|
31
|
+
# array and then run the block on the array. A simple work around to
|
32
|
+
# make `count` work with a block is to call `to_a.count {...}`.
|
33
|
+
#
|
34
|
+
# Example:
|
35
|
+
# Model.where(id: [1, 2, 3].select { |m| m.method == true }.size
|
36
|
+
#
|
37
|
+
# becomes:
|
38
|
+
#
|
39
|
+
# Model.where(id: [1, 2, 3]).to_a.count { |m| m.method == true }
|
40
|
+
class Count < Cop
|
41
|
+
include SafeMode
|
42
|
+
include RangeHelp
|
43
|
+
|
44
|
+
MSG = 'Use `count` instead of `%<selector>s...%<counter>s`.'.freeze
|
45
|
+
|
46
|
+
def_node_matcher :count_candidate?, <<-PATTERN
|
47
|
+
{
|
48
|
+
(send (block $(send _ ${:select :reject}) ...) ${:count :length :size})
|
49
|
+
(send $(send _ ${:select :reject} (:block_pass _)) ${:count :length :size})
|
50
|
+
}
|
51
|
+
PATTERN
|
52
|
+
|
53
|
+
def on_send(node)
|
54
|
+
return if rails_safe_mode?
|
55
|
+
|
56
|
+
count_candidate?(node) do |selector_node, selector, counter|
|
57
|
+
return unless eligible_node?(node)
|
58
|
+
|
59
|
+
range = source_starting_at(node) do
|
60
|
+
selector_node.loc.selector.begin_pos
|
61
|
+
end
|
62
|
+
|
63
|
+
add_offense(node,
|
64
|
+
location: range,
|
65
|
+
message: format(MSG, selector: selector,
|
66
|
+
counter: counter))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def autocorrect(node)
|
71
|
+
selector_node, selector, _counter = count_candidate?(node)
|
72
|
+
selector_loc = selector_node.loc.selector
|
73
|
+
|
74
|
+
return if selector == :reject
|
75
|
+
|
76
|
+
range = source_starting_at(node) { |n| n.loc.dot.begin_pos }
|
77
|
+
|
78
|
+
lambda do |corrector|
|
79
|
+
corrector.remove(range)
|
80
|
+
corrector.replace(selector_loc, 'count')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def eligible_node?(node)
|
87
|
+
!(node.parent && node.parent.block_type?)
|
88
|
+
end
|
89
|
+
|
90
|
+
def source_starting_at(node)
|
91
|
+
begin_pos = if block_given?
|
92
|
+
yield node
|
93
|
+
else
|
94
|
+
node.source_range.begin_pos
|
95
|
+
end
|
96
|
+
|
97
|
+
range_between(begin_pos, node.source_range.end_pos)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop is used to identify usages of
|
7
|
+
# `select.first`, `select.last`, `find_all.first`, and `find_all.last`
|
8
|
+
# and change them to use `detect` instead.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# [].select { |item| true }.first
|
13
|
+
# [].select { |item| true }.last
|
14
|
+
# [].find_all { |item| true }.first
|
15
|
+
# [].find_all { |item| true }.last
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
# [].detect { |item| true }
|
19
|
+
# [].reverse.detect { |item| true }
|
20
|
+
#
|
21
|
+
# `ActiveRecord` compatibility:
|
22
|
+
# `ActiveRecord` does not implement a `detect` method and `find` has its
|
23
|
+
# own meaning. Correcting ActiveRecord methods with this cop should be
|
24
|
+
# considered unsafe.
|
25
|
+
class Detect < Cop
|
26
|
+
include SafeMode
|
27
|
+
|
28
|
+
MSG = 'Use `%<prefer>s` instead of ' \
|
29
|
+
'`%<first_method>s.%<second_method>s`.'.freeze
|
30
|
+
REVERSE_MSG = 'Use `reverse.%<prefer>s` instead of ' \
|
31
|
+
'`%<first_method>s.%<second_method>s`.'.freeze
|
32
|
+
|
33
|
+
def_node_matcher :detect_candidate?, <<-PATTERN
|
34
|
+
{
|
35
|
+
(send $(block (send _ {:select :find_all}) ...) ${:first :last} $...)
|
36
|
+
(send $(send _ {:select :find_all} ...) ${:first :last} $...)
|
37
|
+
}
|
38
|
+
PATTERN
|
39
|
+
|
40
|
+
def on_send(node)
|
41
|
+
return if rails_safe_mode?
|
42
|
+
|
43
|
+
detect_candidate?(node) do |receiver, second_method, args|
|
44
|
+
return unless args.empty?
|
45
|
+
return unless receiver
|
46
|
+
|
47
|
+
receiver, _args, body = *receiver if receiver.block_type?
|
48
|
+
return if accept_first_call?(receiver, body)
|
49
|
+
|
50
|
+
register_offense(node, receiver, second_method)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def autocorrect(node)
|
55
|
+
receiver, first_method = *node
|
56
|
+
|
57
|
+
replacement = if first_method == :last
|
58
|
+
"reverse.#{preferred_method}"
|
59
|
+
else
|
60
|
+
preferred_method
|
61
|
+
end
|
62
|
+
|
63
|
+
first_range = receiver.source_range.end.join(node.loc.selector)
|
64
|
+
|
65
|
+
receiver, _args, _body = *receiver if receiver.block_type?
|
66
|
+
|
67
|
+
lambda do |corrector|
|
68
|
+
corrector.remove(first_range)
|
69
|
+
corrector.replace(receiver.loc.selector, replacement)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def accept_first_call?(receiver, body)
|
76
|
+
caller, _first_method, args = *receiver
|
77
|
+
|
78
|
+
# check that we have usual block or block pass
|
79
|
+
return true if body.nil? && (args.nil? || !args.block_pass_type?)
|
80
|
+
|
81
|
+
lazy?(caller)
|
82
|
+
end
|
83
|
+
|
84
|
+
def register_offense(node, receiver, second_method)
|
85
|
+
_caller, first_method, _args = *receiver
|
86
|
+
range = receiver.loc.selector.join(node.loc.selector)
|
87
|
+
|
88
|
+
message = second_method == :last ? REVERSE_MSG : MSG
|
89
|
+
formatted_message = format(message, prefer: preferred_method,
|
90
|
+
first_method: first_method,
|
91
|
+
second_method: second_method)
|
92
|
+
|
93
|
+
add_offense(node, location: range, message: formatted_message)
|
94
|
+
end
|
95
|
+
|
96
|
+
def preferred_method
|
97
|
+
config.for_cop('Style/CollectionMethods') \
|
98
|
+
['PreferredMethods']['detect'] || 'detect'
|
99
|
+
end
|
100
|
+
|
101
|
+
def lazy?(node)
|
102
|
+
return false unless node
|
103
|
+
|
104
|
+
receiver, method, _args = *node
|
105
|
+
method == :lazy && !receiver.nil?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# This cop checks for double `#start_with?` or `#end_with?` calls
|
7
|
+
# separated by `||`. In some cases such calls can be replaced
|
8
|
+
# with an single `#start_with?`/`#end_with?` call.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# # bad
|
12
|
+
# str.start_with?("a") || str.start_with?(Some::CONST)
|
13
|
+
# str.start_with?("a", "b") || str.start_with?("c")
|
14
|
+
# str.end_with?(var1) || str.end_with?(var2)
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# str.start_with?("a", Some::CONST)
|
18
|
+
# str.start_with?("a", "b", "c")
|
19
|
+
# str.end_with?(var1, var2)
|
20
|
+
class DoubleStartEndWith < Cop
|
21
|
+
MSG = 'Use `%<receiver>s.%<method>s(%<combined_args>s)` ' \
|
22
|
+
'instead of `%<original_code>s`.'.freeze
|
23
|
+
|
24
|
+
def on_or(node)
|
25
|
+
receiver,
|
26
|
+
method,
|
27
|
+
first_call_args,
|
28
|
+
second_call_args = process_source(node)
|
29
|
+
|
30
|
+
return unless receiver && second_call_args.all?(&:pure?)
|
31
|
+
|
32
|
+
combined_args = combine_args(first_call_args, second_call_args)
|
33
|
+
|
34
|
+
add_offense_for_double_call(node, receiver, method, combined_args)
|
35
|
+
end
|
36
|
+
|
37
|
+
def autocorrect(node)
|
38
|
+
_receiver, _method,
|
39
|
+
first_call_args, second_call_args = process_source(node)
|
40
|
+
|
41
|
+
combined_args = combine_args(first_call_args, second_call_args)
|
42
|
+
first_argument = first_call_args.first.loc.expression
|
43
|
+
last_argument = second_call_args.last.loc.expression
|
44
|
+
range = first_argument.join(last_argument)
|
45
|
+
|
46
|
+
lambda do |corrector|
|
47
|
+
corrector.replace(range, combined_args)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def process_source(node)
|
54
|
+
if check_for_active_support_aliases?
|
55
|
+
check_with_active_support_aliases(node)
|
56
|
+
else
|
57
|
+
two_start_end_with_calls(node)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def combine_args(first_call_args, second_call_args)
|
62
|
+
(first_call_args + second_call_args).map(&:source).join(', ')
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_offense_for_double_call(node, receiver, method, combined_args)
|
66
|
+
msg = format(MSG, receiver: receiver.source,
|
67
|
+
method: method,
|
68
|
+
combined_args: combined_args,
|
69
|
+
original_code: node.source)
|
70
|
+
|
71
|
+
add_offense(node, message: msg)
|
72
|
+
end
|
73
|
+
|
74
|
+
def check_for_active_support_aliases?
|
75
|
+
cop_config['IncludeActiveSupportAliases']
|
76
|
+
end
|
77
|
+
|
78
|
+
def_node_matcher :two_start_end_with_calls, <<-PATTERN
|
79
|
+
(or
|
80
|
+
(send $_recv [{:start_with? :end_with?} $_method] $...)
|
81
|
+
(send _recv _method $...))
|
82
|
+
PATTERN
|
83
|
+
|
84
|
+
def_node_matcher :check_with_active_support_aliases, <<-PATTERN
|
85
|
+
(or
|
86
|
+
(send $_recv
|
87
|
+
[{:start_with? :starts_with? :end_with? :ends_with?} $_method]
|
88
|
+
$...)
|
89
|
+
(send _recv _method $...))
|
90
|
+
PATTERN
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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 `String#end_with?`
|
7
|
+
# would suffice.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# 'abc'.match?(/bc\Z/)
|
12
|
+
# 'abc' =~ /bc\Z/
|
13
|
+
# 'abc'.match(/bc\Z/)
|
14
|
+
#
|
15
|
+
# # good
|
16
|
+
# 'abc'.end_with?('bc')
|
17
|
+
class EndWith < Cop
|
18
|
+
MSG = 'Use `String#end_with?` instead of a regex match anchored to ' \
|
19
|
+
'the end of the string.'.freeze
|
20
|
+
SINGLE_QUOTE = "'".freeze
|
21
|
+
|
22
|
+
def_node_matcher :redundant_regex?, <<-PATTERN
|
23
|
+
{(send $!nil? {:match :=~ :match?} (regexp (str $#literal_at_end?) (regopt)))
|
24
|
+
(send (regexp (str $#literal_at_end?) (regopt)) {:match :=~} $_)}
|
25
|
+
PATTERN
|
26
|
+
|
27
|
+
def literal_at_end?(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 end of the string?
|
31
|
+
regex_str =~ /\A(?:#{LITERAL_REGEX})+\\z\z/
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_send(node)
|
35
|
+
return unless redundant_regex?(node)
|
36
|
+
|
37
|
+
add_offense(node)
|
38
|
+
end
|
39
|
+
|
40
|
+
def autocorrect(node)
|
41
|
+
redundant_regex?(node) do |receiver, regex_str|
|
42
|
+
receiver, regex_str = regex_str, receiver if receiver.is_a?(String)
|
43
|
+
regex_str = regex_str[0..-3] # drop \Z anchor
|
44
|
+
regex_str = interpret_string_escapes(regex_str)
|
45
|
+
|
46
|
+
lambda do |corrector|
|
47
|
+
new_source = receiver.source + '.end_with?(' +
|
48
|
+
to_string_literal(regex_str) + ')'
|
49
|
+
corrector.replace(node.source_range, new_source)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Cop
|
5
|
+
module Performance
|
6
|
+
# Do not compute the size of statically sized objects.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # String methods
|
10
|
+
# # bad
|
11
|
+
# 'foo'.size
|
12
|
+
# %q[bar].count
|
13
|
+
# %(qux).length
|
14
|
+
#
|
15
|
+
# # Symbol methods
|
16
|
+
# # bad
|
17
|
+
# :fred.size
|
18
|
+
# :'baz'.length
|
19
|
+
#
|
20
|
+
# # Array methods
|
21
|
+
# # bad
|
22
|
+
# [1, 2, thud].count
|
23
|
+
# %W(1, 2, bar).size
|
24
|
+
#
|
25
|
+
# # Hash methods
|
26
|
+
# # bad
|
27
|
+
# { a: corge, b: grault }.length
|
28
|
+
#
|
29
|
+
# # good
|
30
|
+
# foo.size
|
31
|
+
# bar.count
|
32
|
+
# qux.length
|
33
|
+
#
|
34
|
+
# # good
|
35
|
+
# :"#{fred}".size
|
36
|
+
# CONST = :baz.length
|
37
|
+
#
|
38
|
+
# # good
|
39
|
+
# [1, 2, *thud].count
|
40
|
+
# garply = [1, 2, 3]
|
41
|
+
# garly.size
|
42
|
+
#
|
43
|
+
# # good
|
44
|
+
# { a: corge, **grault }.length
|
45
|
+
# waldo = { a: corge, b: grault }
|
46
|
+
# waldo.size
|
47
|
+
#
|
48
|
+
class FixedSize < Cop
|
49
|
+
MSG = 'Do not compute the size of statically sized objects.'.freeze
|
50
|
+
|
51
|
+
def_node_matcher :counter, <<-MATCHER
|
52
|
+
(send ${array hash str sym} {:count :length :size} $...)
|
53
|
+
MATCHER
|
54
|
+
|
55
|
+
def on_send(node)
|
56
|
+
return if allowed_parent?(node.parent)
|
57
|
+
|
58
|
+
counter(node) do |var, arg|
|
59
|
+
return if allowed_variable?(var) || allowed_argument?(arg)
|
60
|
+
|
61
|
+
add_offense(node)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def allowed_variable?(var)
|
68
|
+
contains_splat?(var) || contains_double_splat?(var)
|
69
|
+
end
|
70
|
+
|
71
|
+
def allowed_argument?(arg)
|
72
|
+
arg && non_string_argument?(arg.first)
|
73
|
+
end
|
74
|
+
|
75
|
+
def allowed_parent?(node)
|
76
|
+
node && (node.casgn_type? || node.block_type?)
|
77
|
+
end
|
78
|
+
|
79
|
+
def contains_splat?(node)
|
80
|
+
return unless node.array_type?
|
81
|
+
|
82
|
+
node.each_child_node(:splat).any?
|
83
|
+
end
|
84
|
+
|
85
|
+
def contains_double_splat?(node)
|
86
|
+
return unless node.hash_type?
|
87
|
+
|
88
|
+
node.each_child_node(:kwsplat).any?
|
89
|
+
end
|
90
|
+
|
91
|
+
def non_string_argument?(node)
|
92
|
+
node && !node.str_type?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|