rubocop-performance 0.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|